Wannafly Winter Camp Day5 Div1 E题 Fast Kronecker Transform 转化为NTT或FFT

(有任何问题欢迎留言或私聊 && 欢迎交流讨论哦

Catalog

@

Problem:传送门

 原题目描述在最下面。
 对给定的式子算解。
\(0\leq k\leq n+m,c_k=(\sum_{i+j=k}i\times j\times \sigma_{a_i,b_j}) mod\;998244353\),其中\(当且仅当a=b时,\sigma_{a_i,b_j}=1。\)

Solution:

 我们发现只有当\(a_i\)\(b_j\)相等时才会对答案造成贡献。

 一共\(2e^5\)个数字,我们枚举每一个数字算贡献,同时分情况讨论:当这个
数字出现次数小于阀值\(T\)时,我们\(O(n^2)\)暴力算;当出现次数大于\(T\)时,我们用\(FFTorNTT\)计算。

 听说这题有人\(FFT\)丢精度就\(wa\)了,我就干脆用\(NTT\)写了,刚好这个模数也是费马质数嘛,接下来看怎么把上述奇怪的卷积转化成一个可以用\(FFT\)\(NTT\)计算的卷积。

 我们把\(a\)数组和\(b\)数组中数字\(x\)所有出现的位置提出来放到新数组里面去,比如\(x\)出现在\(a\)\(1,3,4\)位置,也出现在\(b\)\(2,4,5,6\)位置。

 当\(k\)等于\(5\)时,数字\(x\)产生的贡献\(1*4,2*3\)
 当\(k\)等于\(6\)时,数字\(x\)产生的贡献\(1*5\)

 我们知道一般的卷积式子是长这样的:\(C_k=\sum_{i+j=k}A_i\times B_j\)

 哇哦,感觉这个式子和那个贡献好相似啊。

 我们就构造这样两个数组\(A_{a_i}=a_i,B_{b_j}=b_j\)可以得到:
\(A[]=\{0,1,0,3,4\},B[]=\{0,0,2,0,4,5,6\}\)

 我们对这两个数组求他们多项式乘法的结果,然后将结果的贡献累计到答案数组中就行啦。

 然后当阀值\(T\)\(10000\)时,复杂度就可以承受了。

AC_Code:

感谢日天的板子
这题复杂度的分析dls讲题时分析的很清楚啦,一位群友的笔记截图在下面

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
namespace lh {
#define o2(x) (x)*(x)
    using namespace std;
    typedef long long LL;
    typedef unsigned long long uLL;
    typedef pair<int, LL> pii;
}

using namespace lh;
const int MX = 2e5 + 5;
//const int P = (479 << 21) + 1;
const int P = 998244353;
const int MOD = 998244353;
const int G = 3;
const int NUM = 20;
struct my_NTT {
    LL wn[NUM];
    LL a[MX << 1], b[MX << 1];
    LL pow (LL a, LL x, LL mod) {
        LL ans = 1;
        a %= mod;
        while (x) {
            if (x & 1) ans = ans * a % mod;
            x >>= 1;
            a = a * a % mod;
        }
        return ans;
    }
    //在程序的开头就要放
    void init() {
        for (int i = 0; i < NUM; i++) {
            int t = 1 << i;
            wn[i] = pow (G, (P - 1) / t, P);
        }
    }
    void Rader (LL F[], int len) {
        int j = len >> 1;
        for (int i = 1; i < len - 1; i++) {
            if (i < j) swap (F[i], F[j]);
            int k = len >> 1;
            while (j >= k) j -= k, k >>= 1;
            if (j < k) j += k;
        }
    }
    void NTT (LL F[], int len, int t) {
        Rader (F, len);
        int id = 0;
        for (int h = 2; h <= len; h <<= 1) {
            id++;
            for (int j = 0; j < len; j += h) {
                LL E = 1;
                for (int k = j; k < j + h / 2; k++) {
                    LL u = F[k];
                    LL v = E * F[k + h / 2] % P;
                    F[k] = (u + v) % P;
                    F[k + h / 2] = (u - v + P) % P;
                    E = E * wn[id] % P;
                }
            }
        }
        if (t == -1) {
            for (int i = 1; i < len / 2; i++) swap (F[i], F[len - i]);
            LL inv = pow (len, P - 2, P);
            for (int i = 0; i < len; i++) F[i] = F[i] * inv % P;
        }
    }
    void Conv (LL a[], LL b[], int len) {
        NTT (a, len, 1);
        NTT (b, len, 1);
        for (int i = 0; i < len; i++) a[i] = a[i] * b[i] % P;
        NTT (a, len, -1);
    }
    void gao (LL A[], LL B[], int n, int m, LL ans[]) {//0~n-1
        int len = 1;
        while (len < n + m) len <<= 1;
        for (int i = 0; i < n; i++) a[i] = A[i];
        for (int i = 0; i < m; i++) b[i] = B[i];
        for (int i = n; i < len; i++) a[i] = 0;
        for (int i = m; i < len; i++) b[i] = 0;
        Conv (a, b, len);
        for (int i = 0; i < len; i++) ans[i] = (ans[i]+a[i])%MOD;
    }
}ntt;
const int MXN = 2e5 + 5;
int n, m;
int ar[MXN], br[MXN];
LL A[MXN], B[MXN];
std::vector<int> all[MXN], bll[MXN];
LL ans[MXN];
void solve1(int id) {
    for(int i = 0; i < all[id].size(); ++i) {
        for(int j = 0; j < bll[id].size(); ++j) {
            ans[all[id][i]+bll[id][j]] += (LL)all[id][i] * bll[id][j];
            ans[all[id][i]+bll[id][j]] %= MOD;
        }
    }
}
void solve2(int id) {
    for(int i = 0; i <= n+m; ++i) A[i] = B[i] = 0;
    for(int i = 0; i < all[id].size(); ++i) A[all[id][i]] = all[id][i];
    for(int i = 0; i < bll[id].size(); ++i) B[bll[id][i]] = bll[id][i];
    ntt.gao(A, B, all[id].back()+1, bll[id].back()+1, ans);
}
int main(int argc, char const *argv[]) {
    scanf("%d%d", &n, &m); ++n, ++m;
    ntt.init();
    std::vector<int> vs;
    for(int i = 0; i < n; ++i) scanf("%d", &ar[i]), vs.push_back(ar[i]);
    for(int i = 0; i < m; ++i) scanf("%d", &br[i]), vs.push_back(br[i]);
    sort(vs.begin(), vs.end());
    vs.erase(unique(vs.begin(), vs.end()), vs.end());
    for(int i = 0, tmp; i < n; ++i) {
        tmp = lower_bound(vs.begin(), vs.end(), ar[i]) - vs.begin();
        all[tmp].push_back(i);
    }
    for(int i = 0, tmp; i < m; ++i) {
        tmp = lower_bound(vs.begin(), vs.end(), br[i]) - vs.begin();
        bll[tmp].push_back(i);
    }
    for(int i = 0; i < vs.size(); ++i) {
        if(all[i].size() + bll[i].size() <= 10000) solve1(i);
        else solve2(i);
    }
    for(int i = 0; i <= n + m-2; ++i) printf(i!=n+m-2?"%lld ":"%lld\n", ans[i]);
    return 0;
}


Problem Description:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述



果然,我的\(FFTwa了\)

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
namespace lh {
#define o2(x) (x)*(x)
    using namespace std;
    typedef long long LL;
    typedef unsigned long long uLL;
    typedef pair<int, LL> pii;
}

using namespace lh;
const int MX = 2e5 + 5;
//const int P = (479 << 21) + 1;
const int P = 998244353;
const int MOD = 998244353;
const int G = 3;
const int NUM = 20;

struct my_FFT {
    const double pi = acos (-1.0);
    static const int MM = MX * 4;
    int len, res[MM], mx; //开大4倍
    struct cpx {
        double r, i;
        cpx (double r = 0, double i = 0) : r (r), i (i) {};
        cpx operator+ (const cpx &b) {return cpx (r + b.r, i + b.i);}
        cpx operator- (const cpx &b) {return cpx (r - b.r, i - b.i);}
        cpx operator* (const cpx &b) {return cpx (r*b.r - i*b.i,i*b.r + r * b.i);}
    } va[MM], vb[MM];
    void rader (cpx F[], int len) { //len = 2^M,reverse F[i] with  F[j] j为i二进制反转
        int j = len >> 1;
        for (int i = 1; i < len - 1; ++i) {
            if (i < j) swap (F[i], F[j]); // reverse
            int k = len >> 1;
            while (j >= k) j -= k, k >>= 1;
            if (j < k) j += k;
        }
    }
    void FFT (cpx F[], int len, int t) {
        rader (F, len);
        for (int h = 2; h <= len; h <<= 1) {
            cpx wn (cos (-t * 2 * pi / h), sin (-t * 2 * pi / h) );
            for (int j = 0; j < len; j += h) {
                cpx E (1, 0); //旋转因子
                for (int k = j; k < j + h / 2; ++k) {
                    cpx u = F[k];
                    cpx v = E * F[k + h / 2];
                    F[k] = u + v;
                    F[k + h / 2] = u - v;
                    E = E * wn;
                }
            }
        }
        if (t == -1) //IDFT
            for (int i = 0; i < len; ++i) F[i].r /= len;
    }
    void Conv (cpx a[], cpx b[], int len) { //求卷积
        FFT (a, len, 1);
        FFT (b, len, 1);
        for (int i = 0; i < len; ++i) a[i] = a[i] * b[i];
        FFT (a, len, -1);
    }
    void gao (LL a[], LL b[], int n, int m, LL ans[]) {
        len = 1;
        mx = n + m;
        while (len <= mx) len <<= 1; //mx为卷积后最大下标
        for (int i = 0; i < len; i++) va[i].r =va[i].i = vb[i].r = vb[i].i = 0;
        for (int i = 0; i < n; i++) va[i].r = a[i];//根据题目要求改写
        for (int i = 0; i < m; i++) vb[i].r = b[i];//根据题目要求改写
        Conv (va, vb, len);
        for (int i = 0; i < len; ++i) ans[i] = (ans[i]+(LL)(va[i].r + 0.5))%MOD;
    }

}fft;
const int MXN = 2e5 + 5;
int n, m;
int ar[MXN], br[MXN];
LL A[MXN], B[MXN];
std::vector<int> all[MXN], bll[MXN];
LL ans[MXN*10];
void solve1(int id) {
    for(int i = 0; i < all[id].size(); ++i) {
        for(int j = 0; j < bll[id].size(); ++j) {
            ans[all[id][i]+bll[id][j]] += (LL)all[id][i] * bll[id][j];
            ans[all[id][i]+bll[id][j]] %= MOD;
        }
    }
}
void solve2(int id) {
    for(int i = 0; i <= n+m; ++i) A[i] = B[i] = 0;
    for(int i = 0; i < all[id].size(); ++i) A[all[id][i]] = all[id][i];
    for(int i = 0; i < bll[id].size(); ++i) B[bll[id][i]] = bll[id][i];
    fft.gao(A, B, all[id].back()+1, bll[id].back()+1, ans);
}
int main(int argc, char const *argv[]) {
    scanf("%d%d", &n, &m); ++n, ++m;
    std::vector<int> vs;
    for(int i = 0; i < n; ++i) scanf("%d", &ar[i]), vs.push_back(ar[i]);
    for(int i = 0; i < m; ++i) scanf("%d", &br[i]), vs.push_back(br[i]);
    sort(vs.begin(), vs.end());
    vs.erase(unique(vs.begin(), vs.end()), vs.end());
    for(int i = 0, tmp; i < n; ++i) {
        tmp = lower_bound(vs.begin(), vs.end(), ar[i]) - vs.begin();
        all[tmp].push_back(i);
    }
    for(int i = 0, tmp; i < m; ++i) {
        tmp = lower_bound(vs.begin(), vs.end(), br[i]) - vs.begin();
        bll[tmp].push_back(i);
    }
    for(int i = 0; i < vs.size(); ++i) {
        if(all[i].size() + bll[i].size() <= 10000) solve1(i);
        else solve2(i);
    }
    for(int i = 0; i <= n + m-2; ++i) printf(i!=n+m-2?"%lld ":"%lld\n", ans[i]);
    return 0;
}

转载于:https://www.cnblogs.com/Cwolf9/p/10322102.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值