【原根+FFT】牛客国庆Day1 I Steins;Gate

该博客介绍了如何利用原根和快速傅里叶变换(FFT)来解决一个数值计算问题:在N个数a1, a2, ...,aN中,找出满足a[i]*a[j] ≡ a[k] (mod P)的有序对(i, j),其中P为质数。通过将乘法转换为加法,利用FFT加速计算过程。" 115641126,10429134,VLAN配置:基于IP地址的网络通信分析,"['网络配置', '交换机', 'VLAN划分', '网络通信']
摘要由CSDN通过智能技术生成

S o u c e : Souce: Souce: 牛客国庆集训派对Day1
P r o b l e m : Problem: Problem:
N = 1 e 5 , a 1 , a 2 , . . . , a N N = 1e5,a1,a2,...,aN N=1e5a1,a2,...,aN,对于每一个 a k ak ak,求有多少个有序二元组 ( i , j ) (i,j) (i,j)满足 a [ i ] ∗ a [ j ] = a [ k ] ( m o d P ) a[i]*a[j] = a[k] (mod P) a[i]a[j]=a[k](modP),其中 P P P为一给定质数。
I d e a : Idea: Idea:
1到P-1都可以用P的某一原根的幂次进行表示,那么乘法转化为加法,用FFT加速。
C o d e : Code: Code:

#include<bits/stdc++.h>
using namespace std;

#define I inline
#define pb push_back
typedef double DB;
typedef long long LL;
typedef vector<int> VI;

const int N = 2e5+10;

I int qpow(int a, int b, int P) {
    int ret = 1;
    while(b) {
        if(b&1) ret = 1LL*ret*a%P;
        a = 1LL*a*a%P; b >>= 1;
    }
    return ret;
}

bool isn[N]; int p[N];
I void init() {
    int pnt = 0; isn[1] = 1;
    for(int i = 2; i < N; i++) {
        if(!isn[i]) p[pnt++] = i;
        for(int j = 0; j<pnt && i*p[j]<N; j++) {
            isn[i*p[j]] = 1;
            if(i%p[j] == 0) break;
        }
    }
}

I int findroot(int P) {
    static VI a; a.clear();
    int t = P-1;
    for(int i = 0; p[i]*p[i] <= t; i++) if(t%p[i] == 0) {
        a.pb((P-1)/p[i]);
        while(t%p[i] == 0) t /= p[i];
    }
    if(t > 1) a.pb((P-1)/t);
    for(int g = 2; g < P; g++) {
        bool f = 1;
        for(int &t:a) if(qpow(g, t, P) == 1) { f = 0; break; }
        if(f) return g;
    }
    return -1;
}

I namespace FFT {
    const DB PI = acos(-1.0);
    struct CP {
        DB x,y;
        CP(DB x=0, DB y=0):x(x),y(y){}
        CP operator-(const CP &b) const { return CP(x-b.x, y-b.y); }
        CP operator+(const CP &b) const { return CP(x+b.x, y+b.y); }
        CP operator*(const CP &b) const { return CP(x*b.x-y*b.y, x*b.y+y*b.x); }
    };

    int rev[N<<2];
    I void FFT(CP *A, int n, int op) {
        for(int i = 0; i < n; i++) if(i < rev[i]) swap(A[i], A[rev[i]]);
        for(int i = 1; i < n; i<<=1) {
            CP wn(cos(PI/i), op*sin(PI/i));
            for(int j = 0, R = i<<1; j < n; j += R) {
                CP w(1,0);
                for(int k = 0; k < i; k++, w = w*wn) {
                    CP x = A[j+k], y = w*A[j+i+k];
                    A[j+k] = x+y; A[j+i+k] = x-y;
                }
            }
        }
        if(op == -1) for(int i = 0; i < n; i++) A[i].x /= n;
    }

    CP A[N<<2], B[N<<2];
    I void mul(LL *a, int la, LL *b, int lb) {
        int len = 1; while(len < la+lb) len <<= 1;
        for(int i = 0; i < len; i++) {
            rev[i] = ((rev[i>>1]>>1)|(len>>(i&1)))&(len-1);
            A[i] = a[i], B[i] = b[i];
        }
        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);
        for(int i = 0; i < len; i++) a[i] = (LL)(A[i].x+0.5);
    }
}

int id[N];
LL a[N<<2], arr[N];

I void work() {
    int n, P; scanf("%d%d", &n, &P);
    int g = findroot(P), t = g;
    for(int i = 1; i <= P-1; i++) { id[t] = i; t = 1LL*t*g%P; }
    LL cnt = 0;
    for(int i = 1; i <= n; i++) {
        int x; scanf("%d", &x);
        arr[i] = x; x %= P;
        if(x == 0) cnt++; else a[id[x]]++;
    }
    mul(a, P, a, P);
    for(int i = P; i <= 2*P-2; i++) a[i-P+1] += a[i];
    cnt = 2*cnt*(n-cnt)+cnt*cnt;
    for(int i = 1; i <= n; i++) {
        if(arr[i] >= P) { puts("0"); continue; }
        arr[i] %= P;
        if(arr[i] == 0) printf("%lld\n", cnt);
        else printf("%lld\n", a[id[arr[i]]]);
    }
}

int main() {
    init(); work();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值