加载中...

快速数论变换 NTT

pmalbd0.png

pmGfJ78.png

pma391g.png

pmaKdPg.png

pmaKwGQ.png

基础模板

const int N = 4e6;

ll qpow(ll a, ll b, int p)
{
    ll res = 1;
    while (b){
        if (b & 1) res = res * a % p;
        b >>= 1;
        a = a * a % p;
    }
    return res;
}

const int p = 998244353;
const int g = 3;
const int gi = qpow(g, p - 2, p); // 原根的倒数

int n, m;
ll A[N], B[N];

void NTT(ll A[], int n, int op){
    if(n == 1) return;
    ll A1[n>>1], A2[n>>1];
    for(int i = 0; i < n/2; i ++){
        A1[i] = A[i*2]; 
        A2[i] = A[i*2+1];
    }
    NTT(A1, n/2, op); NTT(A2, n/2, op);
    ll g1 = qpow(op == 1 ? g : gi, (p - 1) / n, p); 
    ll gk = 1;
    for(int i = 0; i < n/2; i ++){
        A[i] = (A1[i] + A2[i] * gk % p) % p;
        A[i + n/2] = (A1[i] - A2[i] * gk % p + p) % p;
        gk = gk * g1 % p;
    }
}


void solve()
{
    cin >> n >> m;
    for(int i = 0; i < n; i ++) cin >> A[i];
    for(int i = 0; i < m; i ++) cin >> B[i];
    n --; m --; // 最高次项为n-1,m-1
    int nn = 1;
    while(nn <= n + m) nn <<= 1;
    NTT(A, nn, 1); NTT(B, nn, 1);
    for(int i = 0; i < nn; i ++){
        A[i] = A[i] * B[i] % p;
    }
    NTT(A, nn, -1);
    for(int i = 0; i <= n + m; i ++){
        cout << A[i] * qpow(nn, p - 2, p) % p << " ";
    }
}

实战模版

const int mod = 998244353;
const int g = 3;

void NTT(vector<int>& a, bool inverse){
    int n = a.size();
    for(int i = 1, j = 0; i < n; i ++){
        int bit = n >> 1;
        for(; j & bit; bit >>= 1) j ^= bit;
        j ^= bit;
        if(i < j) swap(a[i], a[j]);
    }
    for(int len = 2; len <= n; len <<= 1){
        int wlen = qpow(g, (mod - 1) / len, mod);
        if(inverse) wlen = qpow(wlen, mod - 2, mod);
        for(int i = 0; i < n; i += len){
            int w = 1;
            for(int j = 0; j < len / 2; j ++){
                int u = a[i + j];
                int v = (ll)a[i + j + len / 2] * w % mod;
                a[i + j] = u + v;
                if(a[i + j] >= mod) a[i + j] -= mod;
                a[i + j + len / 2] = u - v;
                if(a[i + j + len / 2] < 0) a[i + j + len / 2] += mod;
                w = (ll)w * wlen % mod;
            }
        } 
    }
    if(inverse){
        int inv_n = qpow(n, mod - 2, mod);
        for(int i = 0; i < n; i ++) a[i] = (ll)a[i] * inv_n % mod;
    }
}

vector<int> Poly_mul(const vector<int>& a, const vector<int>& b){
    if(a.empty()) return b;
    if(b.empty()) return a;
    if((ll)a.size() * b.size() <= 5000){
        vector<int> c((int)a.size() + (int)b.size() - 1, 0);
        for(int i = 0; i < (int)a.size(); i ++){
            for(int j = 0; j < (int)b.size(); j ++){
                c[i + j] = (c[i + j] + (ll)a[i] * b[j]) % mod;
            }
        }
        return c;
    }
    int need = (int)a.size() + (int)b.size() - 1;
    int n = 1;
    while(n < need) n <<= 1;
    vector<int> x(a.begin(), a.end()), y(b.begin(), b.end());
    x.resize(n);
    y.resize(n);
    NTT(x, 0);
    NTT(y, 0);
    for(int i = 0; i < n; i ++) x[i] = (ll)x[i] * y[i] % mod;
    NTT(x, 1);
    x.resize(need);
    return x;
}

// 接口:对于两个vector表示的多项式,调用 c = Poly_mul(a, b);

2026牛客寒假训练营6 J

pmaommF.png

NTT 设计

由于是要统计每个偏移量的贡献,因此我们要让多项式中未知数的幂次等于该偏移量。

对于每一种字符 \(c\),设计两个多项式 \(a,b\)

  • 对于多项式 \(a\),让等于字符 \(c\) 的位置系数为 1,其他为 0;
  • 对于多项式 \(b\),让小于字符 \(c\) 的位置系数为 1,其他为 0;

这样,二者相乘时,就可以使得 \(s_{i} > t_{j}\) 的位置对 \((i, j)\) 产生 1 个贡献。

而为了让位置对 \((i, j)\) 的贡献作用在其相应的偏移量上,我们可以考虑将多项式 \(a\) 中每个原来的未知数幂次全部取负,这样两项相乘后得到的未知数幂次可以体现两个位置之间的偏移量。

真正实现时,为了套用模板,不能实际让未知数的幂次取负值。这个处理也很简单:对于负幂次设计一个合适的偏移量,使其变为非负数就行了。

总复杂度 \(O(26 * n\log n)\)(因为还需要枚举字符),具体细节见 code。

code

2025郑州邀请赛K

和上一题类似,又是凯撒加密。只不过本题不是关于位置偏移,而是模 \(m\) 意义下的值域偏移。

考虑计算偏移量为 \(k\) 时的总贡献:

\[C(k) = \sum_{v=0}^{m-1} freq_{(v-k+m)\%m} * value_{v} \]

发现是卷积形式,考虑 NTT。

本题用多项式求解的核心在于:将同一偏移量的所有分贡献累计到某个固定 \(x^{i}\) 的系数中。为了达到这种效果,我们仍需沿用上一道题的思路,将两个因子多项式的其中一个的所有系数反转(这里反转 \(freq\))。此时,偏移量为 \(0\) 的所有分贡献均累计到了 \(x^{m-1}\) 的系数中。

进一步考虑如何表示偏移量为 \(1,2,...,m-1\) 的贡献。这里有一个小 trick:将 \(value\) 数组倍长,倍长后每个长度为 \(m\) 的滑动窗口便对应了某一种偏移量,其中偏移量为 \(k\) 的所有分贡献会被累计到 \(x^{m-1+k}\) 所对应的系数中。于是,将上述两个改造后的多项式相乘,就能得到所有偏移量的结果,答案为这些结果的最大值。总复杂度 \(O(m\log m)\)

code

2026沈阳邀请赛 H

也是与偏移量相关的 NTT,需要自己观察并推导出相关式子。

考虑如何求 \(g(k) = \sum_{i=1}^{n}f(S_{i}, S_{k})\)

枚举固定后缀 \(S_{k}\) 的每一位与哪些 \(S_{i}\) 产生贡献,可以得到:

\[g(k) = \sum_{j=k}^{n}\sum_{i=j-k+1}^{n} [s_{i} = s_{j}] \]

\(g(k)\) 本身不好求,但是会发现其差分 \(g_{k} - g_{k+1}\) 是好求的:

\[g(k) - g(k + 1) = \sum_{i=1}^{n}[s_{k}=s_{i}] - \sum_{j=k+1}^{n}[s_{j-k}=s_{j}] \]

前一项可以简单预处理得到;后一项等价于求原串中偏移量恰好为 \(k\) 的位置对的贡献之和。

NTT 后即可得到对于每一项 \(k \in [1, n)\)\(g(k) - g(k + 1)\);暴力求出 \(g(n)\) 后,即可得到每一项 \(g(k)\) 的值。

注意本题没有取模要求,且答案会超过 998244353,因此只能用 FFT(换用更大模数的 NTT 会 TLE...)

code

ABC462G

结合容斥原理

link

edu118F

结合容斥原理

link

posted @ 2026-06-29 21:12  小橘奏  阅读(9)  评论(0)    收藏  举报