提交记录 84978


用户 题目 状态 得分 用时 内存 语言 代码长度
saffah_dsh_v41_0919 1002. 测测你的多项式乘法 Wrong Answer 0 48.14 us 80 KB C++17 32.90 KB
提交时间 评测时间
2026-09-22 03:12:12 2026-09-22 03:12:15
// 1002: four-step (cache-blocked) NTT mod 81788929, Montgomery arithmetic, AVX2.
// n = m = 1e6; coefficients < 10 so result coefficients (< 8.1e7) are exact in Z_p.
//
// Pipeline (N = N1*N2 = 2^21):
//   L   : natural x[n1*N2+n2] -> T1[n2*N1+n1]   (tiled transpose + Montgomery convert)
//   D1  : DIF_N1 on every row of T1             (natural in, digit-reversed out)
//   P   : T1[n2][p] -> T2[PERM[p]][n2]  AND multiply by w^{k1*n2}   (fused transpose+twiddle)
//   D2  : DIF_N2 on every row of T2
// spectrum: T2[k1*N2+p] = X[k1 + N1*perm2(p)]
// inverse: DIT_N2 -> (fused twiddle+transpose) -> DIT_N1 -> store natural (1/N scale).
//
// All hot kernels use 32-byte ALIGNED 256-bit loads/stores: with the judge's plain
// `-O2` (= -mtune=generic) GCC splits every _mm256_loadu/storeu into two 128-bit
// halves, which costs ~15-20% in the row kernels.  Row starts are 64B aligned
// (strides 2064 and 1040 words = multiples of 64 bytes) and every offset used is a
// multiple of 8 words, so aligned accesses are always valid on our own buffers.
#include <immintrin.h>

#pragma GCC optimize("O3","unroll-loops","rename-registers","peel-loops")
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <atomic>
#include <sched.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <cstdlib>
#ifndef GBAND
#define GBAND 32u          /* E4-A: output rows per load+transform band */
#endif

typedef uint32_t u32;
typedef uint64_t u64;

// Modulus choice: the true convolution coefficients are <= 1000001*81 = 81000081,
// so ANY prime p > 81000081 with 2^21 | p-1 computes them exactly.  We take the
// smallest such prime, 81788929 = 39*2^21+1 < 2^27.  A modulus this small leaves
// enough head-room for fully lazy (reduction-free) butterfly arithmetic.
static const u32 P = 81788929u;
static const u32 RMOD = 41942988u;       // 2^32 mod P  = mont(1)
static const u32 P1M = P - RMOD;         // mont(-1)
static const u32 R2MOD = 56088131u;      // 2^64 mod P
static const u32 NPM = 81788927u;        // -P^{-1} mod 2^32
static u32 OMEGA = 0;                    // primitive 2^21-th root of unity
static u32 IVAL = 0;                     // g^((P-1)/4)  (plain), set in setup
static u32 IVM = 0;                      // mont(I)
static u32 IIVM = 0;                     // mont(-I)

#define AVX2 __attribute__((target("avx2,tune=skylake")))

static inline u32 addm(u32 a, u32 b) { u32 s = a + b; return s >= P ? s - P : s; }
static inline u32 subm(u32 a, u32 b) { return a >= b ? a - b : a + P - b; }
static inline u32 mmul1(u32 a, u32 b) {
    u64 t = (u64)a * b;
    u32 m = (u32)((u32)t * NPM);
    u32 r = (u32)((t + (u64)m * P) >> 32);
    return r >= P ? r - P : r;
}
static u32 fpow(u32 a, u64 e) { u64 r = 1, b = a; while (e) { if (e & 1) r = r * b % P; b = b * b % P; e >>= 1; } return (u32)r; }

// Arithmetic: data are kept in Montgomery form, every stored value lies in [0,2P).
// P = 81788929 < 2^27 so 8P < 2^30: sums used inside a butterfly may grow to 8P
// without any reduction, which removes most of the conditional-subtract ops.
#define PBOUND (2u * P)
AVX2 static inline __m256i vaddp(__m256i a, __m256i b) {   // [0,2P) -> [0,2P)
    const __m256i p2 = _mm256_set1_epi32((int)PBOUND);
    __m256i s = _mm256_add_epi32(a, b);
    return _mm256_min_epu32(s, _mm256_sub_epi32(s, p2));
}
AVX2 static inline __m256i vsubp(__m256i a, __m256i b) {   // [0,2P) -> [0,2P)
    const __m256i p2 = _mm256_set1_epi32((int)PBOUND);
    __m256i d = _mm256_sub_epi32(a, b);
    return _mm256_min_epu32(d, _mm256_add_epi32(d, p2));
}
AVX2 static inline __m256i vaddl(__m256i a, __m256i b) {   // inputs <4P -> <4P (no red.)
    return _mm256_add_epi32(a, b);
}
AVX2 static inline __m256i vsubl(__m256i a, __m256i b) {   // inputs <4P -> (0,6P)
    const __m256i p2 = _mm256_set1_epi32((int)PBOUND);
    return _mm256_add_epi32(_mm256_sub_epi32(a, b), p2);
}
AVX2 static inline __m256i vsub4(__m256i a, __m256i b) {   // inputs <4P -> (0,8P)
    const __m256i p4 = _mm256_set1_epi32((int)(4u * P));
    return _mm256_add_epi32(_mm256_sub_epi32(a, b), p4);
}
AVX2 static inline __m256i vred4(__m256i x) {              // <8P -> <2P
    const __m256i p2 = _mm256_set1_epi32((int)PBOUND);
    const __m256i p4 = _mm256_set1_epi32((int)(4u * P));
    __m256i v = _mm256_min_epu32(x, _mm256_sub_epi32(x, p4));
    return _mm256_min_epu32(v, _mm256_sub_epi32(v, p2));
}
// Montgomery: any input < 2^32, w < P -> result < 1.2P (no final reduction needed)
AVX2 static inline __m256i vmul(__m256i a, __m256i b) {
    const __m256i np = _mm256_set1_epi32((int)NPM);
    const __m256i pv = _mm256_set1_epi32((int)P);
    __m256i ah = _mm256_shuffle_epi32(a, 0xB1);            // odd lanes -> even (port 5)
    __m256i bh = _mm256_shuffle_epi32(b, 0xB1);
    __m256i lo = _mm256_mul_epu32(a, b);
    __m256i hi = _mm256_mul_epu32(ah, bh);
    __m256i ml = _mm256_mul_epu32(lo, np);
    __m256i mh = _mm256_mul_epu32(hi, np);
    __m256i sl = _mm256_add_epi64(lo, _mm256_mul_epu32(ml, pv));
    __m256i sh = _mm256_add_epi64(hi, _mm256_mul_epu32(mh, pv));
    return _mm256_blend_epi32(_mm256_shuffle_epi32(sl, 0x31), sh, 0xAA);
}
// aligned (our buffers: 64B-aligned rows, 8-word offsets) and unaligned variants
AVX2 static inline __m256i aload(const u32 *p) { return _mm256_load_si256((const __m256i *)p); }
AVX2 static inline void astore(u32 *p, __m256i v) { _mm256_store_si256((__m256i *)p, v); }
AVX2 static inline __m256i vload(const u32 *p) { return _mm256_loadu_si256((const __m256i *)p); }
AVX2 static inline void vstore(u32 *p, __m256i v) { _mm256_storeu_si256((__m256i *)p, v); }

AVX2 static inline void tr8x8(__m256i *r) {
    __m256i t0 = _mm256_unpacklo_epi32(r[0], r[1]), t1 = _mm256_unpackhi_epi32(r[0], r[1]);
    __m256i t2 = _mm256_unpacklo_epi32(r[2], r[3]), t3 = _mm256_unpackhi_epi32(r[2], r[3]);
    __m256i t4 = _mm256_unpacklo_epi32(r[4], r[5]), t5 = _mm256_unpackhi_epi32(r[4], r[5]);
    __m256i t6 = _mm256_unpacklo_epi32(r[6], r[7]), t7 = _mm256_unpackhi_epi32(r[6], r[7]);
    __m256i u0 = _mm256_unpacklo_epi64(t0, t2), u1 = _mm256_unpackhi_epi64(t0, t2);
    __m256i u2 = _mm256_unpacklo_epi64(t1, t3), u3 = _mm256_unpackhi_epi64(t1, t3);
    __m256i u4 = _mm256_unpacklo_epi64(t4, t6), u5 = _mm256_unpackhi_epi64(t4, t6);
    __m256i u6 = _mm256_unpacklo_epi64(t5, t7), u7 = _mm256_unpackhi_epi64(t5, t7);
    r[0] = _mm256_permute2x128_si256(u0, u4, 0x20);
    r[1] = _mm256_permute2x128_si256(u1, u5, 0x20);
    r[2] = _mm256_permute2x128_si256(u2, u6, 0x20);
    r[3] = _mm256_permute2x128_si256(u3, u7, 0x20);
    r[4] = _mm256_permute2x128_si256(u0, u4, 0x31);
    r[5] = _mm256_permute2x128_si256(u1, u5, 0x31);
    r[6] = _mm256_permute2x128_si256(u2, u6, 0x31);
    r[7] = _mm256_permute2x128_si256(u3, u7, 0x31);
}

// ---- scalar reference row transforms (small sizes / fallback) ----
static void dif4s(u32 *a, int n, int M, const u32 *T1, const u32 *T2, const u32 *T3) {
    int L = M << 2;
    for (int base = 0; base < n; base += L) {
        u32 *p = a + base;
        for (int j = 0; j < M; j++) {
            u32 x0 = p[j], x1 = p[j + M], x2 = p[j + 2 * M], x3 = p[j + 3 * M];
            u32 t0 = addm(x0, x2), t1 = subm(x0, x2), t2 = addm(x1, x3), t3 = mmul1(subm(x1, x3), IVM);
            p[j] = addm(t0, t2);
            p[j + M] = mmul1(addm(t1, t3), T1[j]);
            p[j + 2 * M] = mmul1(subm(t0, t2), T2[j]);
            p[j + 3 * M] = mmul1(subm(t1, t3), T3[j]);
        }
    }
}
static void dit4s(u32 *a, int n, int M, const u32 *T1, const u32 *T2, const u32 *T3) {
    int L = M << 2;
    for (int base = 0; base < n; base += L) {
        u32 *p = a + base;
        for (int j = 0; j < M; j++) {
            u32 c0 = p[j], c1 = mmul1(p[j + M], T1[j]), c2 = mmul1(p[j + 2 * M], T2[j]), c3 = mmul1(p[j + 3 * M], T3[j]);
            u32 t0 = addm(c0, c2), t1 = subm(c0, c2), t2 = addm(c1, c3), t3 = mmul1(subm(c1, c3), IIVM);
            p[j] = addm(t0, t2);
            p[j + M] = addm(t1, t3);
            p[j + 2 * M] = subm(t0, t2);
            p[j + 3 * M] = subm(t1, t3);
        }
    }
}
AVX2 static void dif4v(u32 *a, int n, int M, const u32 *T1, const u32 *T2, const u32 *T3) {
    const __m256i iv = _mm256_set1_epi32((int)IVM);
    const int L = M << 2;
    for (int base = 0; base < n; base += L) {
        u32 *p = a + base;
        for (int j = 0; j < M; j += 8) {
            __m256i x0 = aload(p + j), x1 = aload(p + j + M), x2 = aload(p + j + 2 * M), x3 = aload(p + j + 3 * M);
            __m256i t0 = vaddl(x0, x2), t1 = vsubl(x0, x2), t2 = vaddl(x1, x3), t3 = vmul(vsubl(x1, x3), iv);
            astore(p + j, vred4(vaddl(t0, t2)));
            astore(p + j + M, vmul(vaddl(t1, t3), aload(T1 + j)));
            astore(p + j + 2 * M, vmul(vsub4(t0, t2), aload(T2 + j)));
            astore(p + j + 3 * M, vmul(vsubl(t1, t3), aload(T3 + j)));
        }
    }
}
AVX2 static void dit4v(u32 *a, int n, int M, const u32 *T1, const u32 *T2, const u32 *T3) {
    const __m256i iv = _mm256_set1_epi32((int)IIVM);
    const int L = M << 2;
    for (int base = 0; base < n; base += L) {
        u32 *p = a + base;
        int j = 0;
        for (; j + 16 <= M; j += 16) {
            __m256i c0 = aload(p + j), d0 = aload(p + j + 8);
            __m256i c1 = vmul(aload(p + j + M), aload(T1 + j));
            __m256i d1 = vmul(aload(p + j + M + 8), aload(T1 + j + 8));
            __m256i c2 = vmul(aload(p + j + 2 * M), aload(T2 + j));
            __m256i d2 = vmul(aload(p + j + 2 * M + 8), aload(T2 + j + 8));
            __m256i c3 = vmul(aload(p + j + 3 * M), aload(T3 + j));
            __m256i d3 = vmul(aload(p + j + 3 * M + 8), aload(T3 + j + 8));
            __m256i ta0 = vaddp(c0, c2), ta1 = vsubp(c0, c2), ta2 = vaddp(c1, c3), ta3 = vmul(vsubp(c1, c3), iv);
            __m256i tb0 = vaddp(d0, d2), tb1 = vsubp(d0, d2), tb2 = vaddp(d1, d3), tb3 = vmul(vsubp(d1, d3), iv);
            astore(p + j, vaddp(ta0, ta2));
            astore(p + j + 8, vaddp(tb0, tb2));
            astore(p + j + M, vaddp(ta1, ta3));
            astore(p + j + M + 8, vaddp(tb1, tb3));
            astore(p + j + 2 * M, vsubp(ta0, ta2));
            astore(p + j + 2 * M + 8, vsubp(tb0, tb2));
            astore(p + j + 3 * M, vsubp(ta1, ta3));
            astore(p + j + 3 * M + 8, vsubp(tb1, tb3));
        }
        for (; j < M; j += 8) {
            __m256i c0 = aload(p + j);
            __m256i c1 = vmul(aload(p + j + M), aload(T1 + j));
            __m256i c2 = vmul(aload(p + j + 2 * M), aload(T2 + j));
            __m256i c3 = vmul(aload(p + j + 3 * M), aload(T3 + j));
            __m256i t0 = vaddp(c0, c2), t1 = vsubp(c0, c2), t2 = vaddp(c1, c3), t3 = vmul(vsubp(c1, c3), iv);
            astore(p + j, vaddp(t0, t2));
            astore(p + j + M, vaddp(t1, t3));
            astore(p + j + 2 * M, vsubp(t0, t2));
            astore(p + j + 3 * M, vsubp(t1, t3));
        }
    }
}
AVX2 static void stage2(u32 *a, int n) {
    for (int i = 0; i < n; i += 8) {
        __m256i v = aload(a + i);
        __m256i t = _mm256_shuffle_epi32(v, 0xB1);
        __m256i s = vaddp(v, t);
        __m256i d = vsubp(t, v);
        astore(a + i, _mm256_blend_epi32(s, d, 0xAA));
    }
}
// L=8 radix-4 stage (M=2): 8 blocks (64 elements) per iteration via 8x8 transposes
// dir bit0: 1 = inverse (input twiddles).  r2: also perform the final radix-2 stage
// (which has no twiddles) on the same 64-element group while it is still in registers.
AVX2 static void tail8(u32 *a, int n, const u32 *T1, const u32 *T2, const u32 *T3, int dir, int r2) {
    const __m256i iv = _mm256_set1_epi32((int)(dir ? IIVM : IVM));
    const __m256i t1 = _mm256_set1_epi32((int)T1[1]), t2 = _mm256_set1_epi32((int)T2[1]), t3 = _mm256_set1_epi32((int)T3[1]);
    for (int g = 0; g < n; g += 64) {
        __m256i r[8];
        for (int i = 0; i < 8; i++) r[i] = aload(a + g + 8 * i);
        tr8x8(r);                                    // r[j] = position j across the 8 blocks
        if (r2 && dir) {                             // inverse: radix-2 stage comes first
            for (int p = 0; p < 8; p += 2) { __m256i x = r[p], y = r[p + 1]; r[p] = vaddp(x, y); r[p + 1] = vsubp(x, y); }
        }
        for (int j = 0; j < 2; j++) {
            __m256i x0 = r[j], x1 = r[j + 2], x2 = r[j + 4], x3 = r[j + 6];
            if (dir && j) { x1 = vmul(x1, t1); x2 = vmul(x2, t2); x3 = vmul(x3, t3); }
            __m256i a0 = vaddp(x0, x2), a1 = vsubp(x0, x2), a2 = vaddp(x1, x3), a3 = vmul(vsubp(x1, x3), iv);
            __m256i b0 = vaddp(a0, a2), b1 = vaddp(a1, a3), b2 = vsubp(a0, a2), b3 = vsubp(a1, a3);
            if (!dir && j) { b1 = vmul(b1, t1); b2 = vmul(b2, t2); b3 = vmul(b3, t3); }
            r[j] = b0; r[j + 2] = b1; r[j + 4] = b2; r[j + 6] = b3;
        }
        if (r2 && !dir) {                            // forward: radix-2 stage comes last
            for (int p = 0; p < 8; p += 2) { __m256i x = r[p], y = r[p + 1]; r[p] = vaddp(x, y); r[p + 1] = vsubp(x, y); }
        }
        tr8x8(r);
        for (int i = 0; i < 8; i++) astore(a + g + 8 * i, r[i]);
    }
}
// L=16 radix-4 stage (M=4): 2 blocks (32 elements) per iteration
AVX2 static void tail16(u32 *a, int n, const u32 *T1, const u32 *T2, const u32 *T3, int dir) {
    const __m256i iv = _mm256_set1_epi32((int)(dir ? IIVM : IVM));
    const __m256i t1 = _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *)T1));
    const __m256i t2 = _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *)T2));
    const __m256i t3 = _mm256_broadcastsi128_si256(_mm_loadu_si128((const __m128i *)T3));
    for (int base = 0; base < n; base += 32) {
        __m256i A = aload(a + base), B = aload(a + base + 8), C = aload(a + base + 16), D = aload(a + base + 24);
        __m256i x0 = _mm256_permute2x128_si256(A, C, 0x20);
        __m256i x1 = _mm256_permute2x128_si256(A, C, 0x31);
        __m256i x2 = _mm256_permute2x128_si256(B, D, 0x20);
        __m256i x3 = _mm256_permute2x128_si256(B, D, 0x31);
        if (dir) { x1 = vmul(x1, t1); x2 = vmul(x2, t2); x3 = vmul(x3, t3); }
        __m256i r0 = vaddp(x0, x2), r1 = vsubp(x0, x2), r2 = vaddp(x1, x3), r3 = vmul(vsubp(x1, x3), iv);
        __m256i b0 = vaddp(r0, r2), b1 = vaddp(r1, r3), b2 = vsubp(r0, r2), b3 = vsubp(r1, r3);
        if (!dir) { b1 = vmul(b1, t1); b2 = vmul(b2, t2); b3 = vmul(b3, t3); }
        astore(a + base, _mm256_permute2x128_si256(b0, b1, 0x20));
        astore(a + base + 8, _mm256_permute2x128_si256(b2, b3, 0x20));
        astore(a + base + 16, _mm256_permute2x128_si256(b0, b1, 0x31));
        astore(a + base + 24, _mm256_permute2x128_si256(b2, b3, 0x31));
    }
}
// L=4 radix-4 stage (M=1, no twiddles): 2 groups (8 elements) per iteration.
// mode 0 = plain stage; mode 1 = square the result in place; mode 2 = result*oth -> dst
// (this is the last forward stage of the N2 transform, so the pointwise step folds in).
AVX2 static void tail4(u32 *a, int n, int dir, const u32 *oth, u32 *dst, int mode) {
    const __m256i K = _mm256_setr_epi32((int)RMOD, (int)(dir ? IIVM : IVM), (int)P1M, (int)(dir ? IVM : IIVM),
                                        (int)RMOD, (int)(dir ? IIVM : IVM), (int)P1M, (int)(dir ? IVM : IIVM));
    for (int i = 0; i < n; i += 8) {
        __m256i v = aload(a + i);
        __m256i w = _mm256_shuffle_epi32(v, 0x4E);
        __m256i s = vaddp(v, w);
        __m256i d = vsubp(v, w);
        __m256i E = _mm256_unpacklo_epi32(s, d);
        __m256i G = _mm256_shuffle_epi32(E, 0xD8);
        __m256i o = vaddp(_mm256_shuffle_epi32(G, 0x88), vmul(_mm256_shuffle_epi32(G, 0xDD), K));
        if (mode == 1) o = vmul(o, o);
        else if (mode == 2) o = vmul(o, aload(oth + i));
        if (mode == 2) astore(dst + i, o); else astore(a + i, o);
    }
}
AVX2 static void pointwise(u32 *p, const u32 *q, int n, int same) {
    if (same) for (int i = 0; i < n; i += 8) { __m256i x = aload(p + i); astore(p + i, vmul(x, x)); }
    else for (int i = 0; i < n; i += 8) astore(p + i, vmul(aload(p + i), aload(q + i)));
}

// ---- plans ----
struct StageTab { int M; const u32 *w1, *w2, *w3; };
struct RowPlan { int n; int ns; int r2; StageTab st[8]; };

static u32 *buildPlan(RowPlan &pl, int n, int inv, u32 *pool) {
    int Ls[8], c = 0, L = n;
    while (L >= 4) { Ls[c++] = L; L >>= 2; }
    pl.n = n; pl.r2 = (L == 2); pl.ns = c;
    u32 *p = pool;
    for (int k = 0; k < c; k++) {
        int Li = inv ? Ls[c - 1 - k] : Ls[k];
        int M = Li >> 2;
        u32 wl = fpow(OMEGA, (u32)((1 << 21) / Li));
        if (inv) wl = fpow(wl, P - 2);
        p = (u32 *)(((uintptr_t)p + 31) & ~(uintptr_t)31);   // tables must be 32B aligned
        u32 *t1 = p, *t2 = p + M, *t3 = p + 2 * M; p += 3 * M;
        pl.st[k].M = M; pl.st[k].w1 = t1; pl.st[k].w2 = t2; pl.st[k].w3 = t3;
        u64 cur = 1;
        for (int j = 0; j < M; j++) {
            u32 v1 = (u32)cur, v2 = (u32)((u64)v1 * v1 % P), v3 = (u32)((u64)v2 * v1 % P);
            t1[j] = (u32)((u64)v1 * RMOD % P);
            t2[j] = (u32)((u64)v2 * RMOD % P);
            t3[j] = (u32)((u64)v3 * RMOD % P);
            cur = cur * wl % P;
        }
    }
    return p;
}

AVX2 static void rowFwd(u32 *a, const RowPlan &pl) {
    int n = pl.n;
    int fused = 0;
    for (int s = 0; s < pl.ns; s++) {
        int M = pl.st[s].M;
        int last = (s == pl.ns - 1);
        if (M >= 8) dif4v(a, n, M, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3);
        else if (M == 4 && n >= 32) tail16(a, n, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3, 0);
        else if (M == 2 && n >= 64) { tail8(a, n, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3, 0, last && pl.r2); fused = last && pl.r2; }
        else if (M == 1 && n >= 8) tail4(a, n, 0, 0, 0, 0);
        else dif4s(a, n, M, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3);
    }
    if (pl.r2 && !fused) stage2(a, n);
}
// like rowFwd but the LAST stage also performs the pointwise step of the convolution:
//   mode 1 : square in place;  mode 2 : a*prod -> prod   (prod = the other spectrum row)
AVX2 static void rowFwdEnd(u32 *a, const RowPlan &pl, int mode, u32 *prod) {
    int n = pl.n;
    for (int s = 0; s < pl.ns - 1; s++) {
        int M = pl.st[s].M;
        if (M >= 8) dif4v(a, n, M, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3);
        else if (M == 4 && n >= 32) tail16(a, n, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3, 0);
        else if (M == 2 && n >= 64) tail8(a, n, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3, 0, 0);
        else if (M == 1 && n >= 8) tail4(a, n, 0, 0, 0, 0);
        else dif4s(a, n, M, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3);
    }
    int M = pl.st[pl.ns - 1].M;
    if (pl.r2) { stage2(a, n); pointwise(a, prod, n, mode == 1); return; }
    if (M == 1 && n >= 8) { tail4(a, n, 0, prod, prod, mode); return; }
    if (M == 4 && n >= 32) tail16(a, n, pl.st[pl.ns - 1].w1, pl.st[pl.ns - 1].w2, pl.st[pl.ns - 1].w3, 0);
    else if (M == 2 && n >= 64) tail8(a, n, pl.st[pl.ns - 1].w1, pl.st[pl.ns - 1].w2, pl.st[pl.ns - 1].w3, 0, 0);
    else dif4s(a, n, M, pl.st[pl.ns - 1].w1, pl.st[pl.ns - 1].w2, pl.st[pl.ns - 1].w3);
    pointwise(a, prod, n, mode == 1);
}
AVX2 static void rowInv(u32 *a, const RowPlan &pl) {
    int n = pl.n;
    int fused = 0;
    if (pl.r2 && !(pl.st[0].M == 2 && n >= 64)) stage2(a, n);
    for (int s = 0; s < pl.ns; s++) {
        int M = pl.st[s].M;
        int first = (s == 0);
        if (M >= 8) dit4v(a, n, M, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3);
        else if (M == 4 && n >= 32) tail16(a, n, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3, 1);
        else if (M == 2 && n >= 64) { tail8(a, n, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3, 1, first && pl.r2); fused = first && pl.r2; }
        else if (M == 1 && n >= 8) tail4(a, n, 1, 0, 0, 0);
        else dit4s(a, n, M, pl.st[s].w1, pl.st[s].w2, pl.st[s].w3);
    }
    (void)fused;
}

// ---- global config ----
static int N1 = 2048, N2 = 1024;
static int S1 = 2048 + 16, S2 = 1024 + 16;   // padded row strides (multiples of 16 words = 64B)
static u32 *BUF0, *BUF1, *BUF2;
static u32 *PERM;
static u32 *CVF, *CVI;                 // mont(w^{+/-PERM[c]}) : fused-transpose twiddle ladders
static u32 SCLN;                       // mont(1/N)
static RowPlan fwd1, inv1, fwd2, inv2;

static void buildPerm(int n, u32 *perm) {
    int Ls[8], rs[8], c = 0, L = n;
    while (L >= 4) { Ls[c] = L; rs[c] = 4; c++; L >>= 2; }
    if (L == 2) { Ls[c] = 2; rs[c] = 2; c++; }
    for (int p = 0; p < n; p++) {
        long k = 0, R = 1;
        for (int i = 0; i < c; i++) { k += (long)((p % Ls[i]) / (Ls[i] / rs[i])) * R; R *= rs[i]; }
        perm[p] = (u32)k;
    }
}

// natural x -> T1[n2*N1+n1] in Montgomery form, zero padded (16x16 macro tiles)
AVX2 static void loadNatBand(const u32 *x, int len, int g0, int g1) {
    const __m256i r2 = _mm256_set1_epi32((int)R2MOD);
    const __m256i z = _mm256_setzero_si256();
    u32 tmp[8];
    int full = (len / N2) & ~7;                    // n1 rows entirely below len (multiple of 8)
    if (full > N1) full = N1;
    for (int n1 = 0; n1 < N1; n1 += 16) {
        int mode = (n1 + 16 <= full) ? 0 : (n1 >= full + 16 ? 2 : 1);   // 0 fast, 1 boundary, 2 zero
        for (int n2 = g0; n2 < g1; n2 += 16)
            for (int rh = 0; rh < 2; rh++)
                for (int h = 0; h < 2; h++) {
                    __m256i tile[8];
                    int r0 = n1 + rh * 8, c0 = n2 + h * 8;
                    if (mode == 0) {
                        for (int i = 0; i < 8; i++) { _mm_prefetch((const char *)(x + (size_t)(r0 + i) * N2 + c0 + 128), _MM_HINT_T0); tile[i] = vload(x + (size_t)(r0 + i) * N2 + c0); }
                    } else if (mode == 2) {
                        for (int i = 0; i < 8; i++) tile[i] = z;
                    } else {
                        for (int i = 0; i < 8; i++) {
                            int base = (r0 + i) * N2 + c0;
                            if (base + 8 <= len) tile[i] = vload(x + base);
                            else { for (int t = 0; t < 8; t++) tmp[t] = (base + t < len) ? x[base + t] : 0; tile[i] = vload(tmp); }
                        }
                    }
                    tr8x8(tile);
                    if (mode == 2) for (int i = 0; i < 8; i++) astore(BUF0 + (size_t)(c0 + i) * S1 + r0, z);
                    else for (int i = 0; i < 8; i++) astore(BUF0 + (size_t)(c0 + i) * S1 + r0, vmul(tile[i], r2));
                }
    }
}
AVX2 static void loadNat(const u32 *x, int len) { loadNatBand(x, len, 0, N2); }
AVX2 static void storeNat(u32 *out, int outlen) {
    const __m256i sc = _mm256_set1_epi32((int)SCLN);
    const __m256i pv = _mm256_set1_epi32((int)P);
    u32 tmp[8];
    for (int n1 = 0; n1 < N1; n1 += 16)
        for (int n2 = 0; n2 < N2; n2 += 16)
            for (int rh = 0; rh < 2; rh++)
                for (int h = 0; h < 2; h++) {
                    __m256i tile[8];
                    int r0 = n1 + rh * 8, c0 = n2 + h * 8;
                    for (int i = 0; i < 8; i++) { _mm_prefetch((const char *)(BUF0 + (size_t)((c0 + 16 + i) & (N2 - 1)) * S1 + r0), _MM_HINT_T0); tile[i] = aload(BUF0 + (size_t)(c0 + i) * S1 + r0); }
                    tr8x8(tile);
                    for (int i = 0; i < 8; i++) {
                        int base = (r0 + i) * N2 + c0;
                        __m256i v = vmul(tile[i], sc);
                        v = _mm256_min_epu32(v, _mm256_sub_epi32(v, pv));   // [0,2P) -> [0,P)
                        if (base + 8 <= outlen) vstore(out + base, v);
                        else { vstore(tmp, v); for (int t = 0; t < 8 && base + t < outlen; t++) out[base + t] = tmp[t]; }
                    }
                }
}
// transpose + twiddle in one pass.
//  forward: src = BUF0[n2][n1] (stride S1) -> dst = BUF1[k1][n2] (stride S2),
//           multiplies element (k1,n2) by w^{k1*n2}; applied on the source rows:
//           for a fixed block of 8 source columns c0..c0+7 (owning k1 = PERM[c0+i]),
//           the twiddle vector advances by one source row with a per-lane constant.
AVX2 static void transpFT(const u32 *src, u32 *dst, int c0lo, int c0hi) {
    for (int c0 = c0lo; c0 < c0hi; c0 += 8) {
        // ladder vectors CV^0..CV^7 for this block of 8 source columns (CV^j is independent
        // of r0), so the per-row twiddle is Q*CV^j with a single serial update per tile.
        __m256i pw[8] __attribute__((aligned(32)));
        const __m256i CV = aload(CVF + c0);
        pw[0] = _mm256_set1_epi32((int)RMOD);
        for (int j = 1; j < 8; j++) pw[j] = vmul(pw[j - 1], CV);
        const __m256i CV8 = vmul(pw[7], CV);
        __m256i Q = pw[0];
        const u32 *k1p = PERM + c0;
        for (int r0 = 0; r0 < N2; r0 += 8) {
            __m256i t[8];
            const u32 *s = src + (size_t)r0 * S1 + c0;
            if (r0 + 64 < N2)
                for (int j = 0; j < 8; j++) _mm_prefetch((const char *)(s + (size_t)(j + 32) * S1), _MM_HINT_T0);
            for (int j = 0; j < 8; j++) t[j] = vmul(aload(s + (size_t)j * S1), vmul(Q, pw[j]));
            Q = vmul(Q, CV8);
            tr8x8(t);
            u32 *d = dst + r0;
            for (int i = 0; i < 8; i++) astore(d + (size_t)k1p[i] * S2, t[i]);
        }
    }
}
//  inverse: src = BUF1[k1][n2] (stride S2) -> dst = BUF0[n2][c] (stride S1, c = digit-reversed k1),
//           multiplies element (k1,n2) by w^{-k1*n2}; applied after the transpose, where a
//           destination vector holds 8 consecutive n2 for a fixed block of k1 = PERM[c0+i].
AVX2 static void transpIT(const u32 *src, u32 *dst, int c0lo, int c0hi) {
    for (int c0 = c0lo; c0 < c0hi; c0 += 8) {
        __m256i pw[8] __attribute__((aligned(32)));
        const __m256i CV = aload(CVI + c0);
        pw[0] = _mm256_set1_epi32((int)RMOD);
        for (int j = 1; j < 8; j++) pw[j] = vmul(pw[j - 1], CV);
        const __m256i CV8 = vmul(pw[7], CV);
        __m256i Q = pw[0];
        const u32 *k1p = PERM + c0;
        for (int r0 = 0; r0 < N2; r0 += 8) {
            __m256i t[8];
            for (int j = 0; j < 8; j++) {
                const u32 *sp = src + (size_t)k1p[j] * S2 + r0;
                _mm_prefetch((const char *)(sp + 512), _MM_HINT_T0);
                t[j] = aload(sp);
            }
            tr8x8(t);
            u32 *d = dst + (size_t)r0 * S1 + c0;
            for (int i = 0; i < 8; i++) astore(d + (size_t)i * S1, vmul(t[i], vmul(Q, pw[i])));
            Q = vmul(Q, CV8);
        }
    }
}


// ---------------- adaptive multithreading: clone() + atomic barrier ----------------
// The judge measures wall time on a 4-core i3-8100, so threads are a real speedup, but
// they must be adaptive: if the process is pinned to one core we stay single-threaded
// and behave exactly like the previous version.
static int NT = 1;
static std::atomic<int> g_bar{0};
static std::atomic<unsigned> g_gen{0};
static inline void barrier() {
    if (NT == 1) return;
    unsigned g = g_gen.load(std::memory_order_acquire);
    if (g_bar.fetch_add(1, std::memory_order_acq_rel) == NT - 1) {
        g_bar.store(0, std::memory_order_release);
        g_gen.fetch_add(1, std::memory_order_release);
    } else {
        while (g_gen.load(std::memory_order_acquire) == g) _mm_pause();
    }
}
static inline int rlo(int tid, int K) { return (int)((long long)K * tid / NT); }
static inline int rhi(int tid, int K) { return (int)((long long)K * (tid + 1) / NT); }

static const u32 *g_a, *g_b; static u32 *g_c; static int g_la, g_lb, g_lc, g_same;
#define PLIMIT 0
__attribute__((noinline)) static void sink_use(u32 *p) {
    static volatile u32 v = 12345;
    v = p[(v >> 3) & 4095];
}
#define TRUNC(k) do { if (PLIMIT == (k)) { if (tid == 0) { sink_use(BUF0); sink_use(BUF1); sink_use(BUF2); } return; } } while (0)

static void convBody(int tid);
static int threadEntry(void *arg) { convBody((int)(long)arg); syscall(SYS_exit, 0); return 0; }

AVX2 static void convBody(int tid) {
    const u32 *a = g_a, *b = g_b; u32 *c = g_c; int la = g_la, lb = g_lb, lc = g_lc, same = g_same;
    const int nb = N1 >> 3;
    { int lo = rlo(tid, N2), hi = rhi(tid, N2);
      for (int g = lo; g < hi; g += (int)GBAND) {
          int g1 = g + (int)GBAND; if (g1 > hi) g1 = hi;
          loadNatBand(a, la, g, g1);          /* the band stays in L2/L3 for its row transform */
          for (int n2 = g; n2 < g1; n2++) rowFwd(BUF0 + (size_t)n2 * S1, fwd1);
      } }
    TRUNC(1);
    if (NT > 1) barrier();
    transpFT(BUF0, BUF1, rlo(tid, nb) << 3, rhi(tid, nb) << 3);
    TRUNC(2);
    if (NT > 1) barrier();
    if (same) {
        { int lo = rlo(tid, N1), hi = rhi(tid, N1);
          for (int k1 = lo; k1 < hi; k1++) rowFwdEnd(BUF1 + (size_t)k1 * S2, fwd2, 1, 0); }
    } else {
        { int lo = rlo(tid, N1), hi = rhi(tid, N1);
          for (int k1 = lo; k1 < hi; k1++) rowFwd(BUF1 + (size_t)k1 * S2, fwd2); }
        TRUNC(3);
        if (NT > 1) barrier();
        { int lo = rlo(tid, N2), hi = rhi(tid, N2);
          for (int g = lo; g < hi; g += (int)GBAND) {
              int g1 = g + (int)GBAND; if (g1 > hi) g1 = hi;
              loadNatBand(b, lb, g, g1);
              for (int n2 = g; n2 < g1; n2++) rowFwd(BUF0 + (size_t)n2 * S1, fwd1);
          } }
        TRUNC(4);
        if (NT > 1) barrier();
        transpFT(BUF0, BUF2, rlo(tid, nb) << 3, rhi(tid, nb) << 3);
        TRUNC(5);
        if (NT > 1) barrier();
        { int lo = rlo(tid, N1), hi = rhi(tid, N1);
          for (int k1 = lo; k1 < hi; k1++) rowFwdEnd(BUF2 + (size_t)k1 * S2, fwd2, 2, BUF1 + (size_t)k1 * S2); }
        TRUNC(6);
    }
    if (NT > 1) barrier();
    { int lo = rlo(tid, N1), hi = rhi(tid, N1);
      for (int k1 = lo; k1 < hi; k1++) rowInv(BUF1 + (size_t)k1 * S2, inv2); }
    TRUNC(7);
    if (NT > 1) barrier();
    transpIT(BUF1, BUF0, rlo(tid, nb) << 3, rhi(tid, nb) << 3);
    TRUNC(8);
    if (NT > 1) barrier();
    { int lo = rlo(tid, N2), hi = rhi(tid, N2);
      for (int n2 = lo; n2 < hi; n2++) rowInv(BUF0 + (size_t)n2 * S1, inv1); }
    TRUNC(9);
    if (NT > 1) barrier();
    if (tid == 0) storeNat(c, lc);
    if (NT > 1) barrier();
}

AVX2 static void conv(const u32 *a, int la, const u32 *b, int lb, u32 *c, int lc) {
    g_a = a; g_b = b; g_c = c; g_la = la; g_lb = lb; g_lc = lc;
    g_same = (la == lb) && (memcmp(a, b, (size_t)la * 4) == 0);
    NT = 3;
    { cpu_set_t cs; if (sched_getaffinity(0, sizeof(cs), &cs) == 0) { int cc = CPU_COUNT(&cs); if (cc < NT) NT = cc; } }
    if (const char *e = getenv("NT")) { int v = atoi(e); if (v >= 1 && v <= 4) NT = v; }
    int spawned = 0;
    if (NT > 1) {
        for (long i = 1; i < NT; i++) {
            void *stk = mmap(0, 1 << 20, PROT_READ | PROT_WRITE,
                             MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
            if (stk == MAP_FAILED) break;
            long r = clone(threadEntry, (char *)stk + (1 << 20),
                           CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM,
                           (void *)i);
            if (r < 0) break;
            spawned++;
        }
    }
    NT = spawned + 1;
    convBody(0);
}

static void setup(int n1, int n2, u32 *b0, u32 *b1, u32 *b2, u32 *perm, u32 *cvf, u32 *cvi, u32 *pool) {
    N1 = n1; N2 = n2; BUF0 = b0; BUF1 = b1; BUF2 = b2; PERM = perm; CVF = cvf; CVI = cvi;
    S1 = n1 + 16; S2 = n2 + 16;
    // element of order exactly 2^21: (P-1) = 39 * 2^21
    for (u32 g = 3;; g += 2) {
        u32 t = fpow(g, (P - 1) >> 21);
        if (fpow(t, 1u << 20) == P - 1) { OMEGA = t; break; }
    }
    IVAL = fpow(OMEGA, 1u << 19);                    // I^2 = -1
    IVM = (u32)((u64)IVAL * RMOD % P);
    IIVM = P - IVM;
    buildPerm(n1, perm);
    u32 *p = pool;
    p = buildPlan(fwd1, n1, 0, p);
    p = buildPlan(inv1, n1, 1, p);
    p = buildPlan(fwd2, n2, 0, p);
    p = buildPlan(inv2, n2, 1, p);
    SCLN = fpow((u32)(n1 * n2), P - 2);   // plain 1/N (buffer holds Montgomery form)
    // cvf[c] = mont(w^{PERM[c]}), cvi[c] = mont(w^{-PERM[c]}): per-lane twiddle ladders for
    // the fused transposes (cvf/cvi are indexed by the digit-reversed source column c).
    u32 w = OMEGA, wi = fpow(w, P - 2);
    u64 cur = 1, curi = 1;
    static u32 fr[4096], ir[4096];   // static: avoid a 32KB stack frame
    for (int k = 0; k < n1; k++) {
        fr[k] = (u32)(cur * RMOD % P);
        ir[k] = (u32)(curi * RMOD % P);
        cur = cur * w % P;
        curi = curi * wi % P;
    }
    for (int c = 0; c < n1; c++) { cvf[c] = fr[perm[c]]; cvi[c] = ir[perm[c]]; }
}


// ---------------- judge entry ----------------
#define MAXN (2170000)
static u32 g_buf0[MAXN] __attribute__((aligned(64)));
static u32 g_buf1[MAXN] __attribute__((aligned(64)));
static u32 g_buf2[MAXN] __attribute__((aligned(64)));
static u32 g_perm[4096] __attribute__((aligned(64)));
static u32 g_cvf[4096] __attribute__((aligned(64)));
static u32 g_cvi[4096] __attribute__((aligned(64)));
static u32 g_pool[80000] __attribute__((aligned(64)));
static int g_ready = 0;

static void ensure(void) {
    if (g_ready) return;
    g_ready = 1;
    setup(512, 4096, g_buf0, g_buf1, g_buf2, g_perm, g_cvf, g_cvi, g_pool);
}

static void convScalar(const u32 *a, int la, const u32 *b, int lb, u32 *c) {
    int lc = la + lb - 1;
    for (int i = 0; i < lc; i++) c[i] = 0;
    for (int i = 0; i < la; i++) {
        u64 ai = a[i];
        if (!ai) continue;
        for (int j = 0; j < lb; j++) c[i + j] = (u32)((c[i + j] + ai * b[j]) % P);
    }
}

void poly_multiply(unsigned *a, int n, unsigned *b, int m, unsigned *c) {
    int la = n + 1, lb = m + 1, lc = la + lb - 1;
    if (lc <= 1024 || (u64)la * lb <= 200000) { convScalar(a, la, b, lb, c); return; }
    ensure();
#if PLIMIT == 0
    sink_use(g_buf0); sink_use(g_buf1); sink_use(g_buf2);
    return;
#endif
    conv(a, la, b, lb, c, lc);
}

CompilationN/AN/ACompile OKScore: N/A

Testcase #148.14 us80 KBWrong AnswerScore: 0


Judge Duck Online | 评测鸭在线
Server Time: 2026-09-22 05:52:09 | Loaded in 1 ms | Server Status
个人娱乐项目,仅供学习交流使用 | 捐赠