zoukankan      html  css  js  c++  java
  • MTT:任意模数NTT

    MTT:任意模数NTT

    概述

    有时我们用FFT处理的数据很大,而模数可以分解为(acdot 2^k+1)的形式。次数用FFT精度不够,用NTT又找不到足够大的模数,于是MTT就应运而生了。

    MTT没有模数的限制,比NTT更加自由,应用广泛,可以用于任意模数或很大的数。

    MTT

    MTT是基于NTT的,其思想很简单,就是做多次NTT,每次使用不同的素数,然后使用CRT合并解,在合并的过程中模最终模数,或是对于无模数的情况使用高精度。

    做NTT的次数取决于最大可能答案的大小,所用的所有素数之积必须大于答案

    实现

    此处以取三个素数为例
    我们可以做三次NTT,相邻次之间改变素数,但这样常数太大,于是我们常常选择封装(适合于模数不太多的情况)。
    我们定义一个结构体node,有三个成员a,b,c,分别代表三个模数下的值,同时,我们定义模数的结构体与之一一对应。

    struct node{
    	LL a,b,c;
    	node(){
    		a=b=c=0;
    	}
    	node(LL x){
    		a=b=c=x;
    	}
    	node(LL x,LL y,LL z){
    		a=x;
    		b=y;
    		c=z;
    	}
    }MOD=node(167772161,469762049,998244353),BASE=node(3),INV=node(116878283,426037461,929031873);
    

    我们还要定义关于此结构体的运算,其中成员之间互不影响,只和操作对象里对应的成员产生运算

    inline node operator+(node x,node y){
    	return node(x.a+y.a,x.b+y.b,x.c+y.c);
    }
    inline node operator-(node x,node y){
    	return node(x.a-y.a,x.b-y.b,x.c-y.c);
    }
    inline node operator*(node x,node y){
    	return node(x.a*y.a%MOD.a,x.b*y.b%MOD.b,x.c*y.c%MOD.c);
    }
    inline node operator%(node x,node y){
    	return node(x.a%y.a,x.b%y.b,x.c%y.c);
    }
    inline node operator/(node x,node y){
    	return node(x.a/y.a,x.b/y.b,x.c/y.c);
    }
    inline node operator-(node x,LL y){
    	return node(x.a-y,x.b-y,x.c-y);
    }
    inline node operator*(node x,LL y){
    	return node(x.a*y,x.b*y,x.c*y);
    }
    inline node operator/(node x,LL y){
    	return node(x.a/y,x.b/y,x.c/y);
    }
    inline node operator%(node x,LL y){
    	return node(x.a%y,x.b%y,x.c%y);
    }
    

    然后套用NTT的板子,最后用CRT合并。

    假设这一位的答案是(x),三个模数分别为(A,B,C),那么:

    [egin{aligned}xequiv x_1pmod{A} \ xequiv x_2pmod{B} \ xequiv x_3pmod{C}end{aligned} ]

    先把前两个合并:

    [egin{aligned}x_1+k_1A=x_2+k_2B\x_1+k_1Aequiv x_2pmod{B}\k_1equiv frac{x_2-x_1}Apmod{B}end{aligned} ]

    于是求出了(k_1),也就求出了(xequiv x_1+k_1Apmod{AB}),记(x_4=x_1+k_1A)

    [egin{aligned}x_4+k_4AB=x_3+k_3C\x_4+k_4ABequiv x_3pmod{C}\k_4equiv dfrac{x_3-x_4}{AB}pmod{C}end{aligned} ]

    因为(x<ABC),所以

    [x=x_4+k_4AB ]

    LL CRT(node x){
    	LL mod1=MOD.a,mod2=MOD.b,mod3=MOD.c,mod_1_2=mod1*mod2;
    	LL inv_1=inv(mod1,mod2),inv_2=inv(mod1*mod2%mod3,mod3);
    	LL A=x.a,B=x.b,C=x.c;
    	LL x4=(B-A+mod2)%mod2*inv_1%mod2*mod1+A;
    	return ((C-x4%mod3+mod3)%mod3*inv_2%mod3*(mod_1_2%Last_Mod)+Last_Mod+x4)%Last_Mod;
    }
    

    于是我们就能写出完整代码了。

    // luogu-judger-enable-o2
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int INF=1e9+7,MAXN=3e6+10/*Min:2^20+10*/;
    void exgcd(LL a,LL b,LL &x,LL &y){
    	if(!b){
    		x=1;
    		y=0;
    		return;
    	}
    	exgcd(b,a%b,y,x);
    	y-=a/b*x;
    }
    inline LL inv(LL x,LL p){
    	LL a,b;
    	exgcd(x,p,a,b);
    	return (a%p+p)%p;
    }
    struct node{
    	LL a,b,c;
    	node(){
    		a=b=c=0;
    	}
    	node(LL x){
    		a=b=c=x;
    	}
    	node(LL x,LL y,LL z){
    		a=x;
    		b=y;
    		c=z;
    	}
    }MOD=node(167772161,469762049,998244353),BASE=node(3),INV=node(116878283,426037461,929031873);
    inline node operator+(node x,node y){
    	return node(x.a+y.a,x.b+y.b,x.c+y.c);
    }
    inline node operator-(node x,node y){
    	return node(x.a-y.a,x.b-y.b,x.c-y.c);
    }
    inline node operator*(node x,node y){
    	return node(x.a*y.a%MOD.a,x.b*y.b%MOD.b,x.c*y.c%MOD.c);
    }
    inline node operator%(node x,node y){
    	return node(x.a%y.a,x.b%y.b,x.c%y.c);
    }
    inline node operator/(node x,node y){
    	return node(x.a/y.a,x.b/y.b,x.c/y.c);
    }
    inline node operator-(node x,LL y){
    	return node(x.a-y,x.b-y,x.c-y);
    }
    inline node operator*(node x,LL y){
    	return node(x.a*y,x.b*y,x.c*y);
    }
    inline node operator/(node x,LL y){
    	return node(x.a/y,x.b/y,x.c/y);
    }
    inline node operator%(node x,LL y){
    	return node(x.a%y,x.b%y,x.c%y);
    }
    LL Last_Mod;
    LL CRT(node x){
    	LL mod1=MOD.a,mod2=MOD.b,mod3=MOD.c,mod_1_2=mod1*mod2;
    	LL inv_1=inv(mod1,mod2),inv_2=inv(mod1*mod2%mod3,mod3);
    	LL A=x.a,B=x.b,C=x.c;
    	LL x4=(B-A+mod2)%mod2*inv_1%mod2*mod1+A;
    	return ((C-x4%mod3+mod3)%mod3*inv_2%mod3*(mod_1_2%Last_Mod)+Last_Mod+x4)%Last_Mod;
    }
    inline LL fpm_(LL base,LL p,LL mod){
    	LL ret=1;
    	while(p){
    		if(p&1)
    			ret=ret*base%mod;
    		base=base*base%mod;
    		p>>=1;
    	}
    	return ret%mod;
    }
    inline node fpm(LL base,node p){
    	return node(fpm_(base,p.a,MOD.a),fpm_(base,p.b,MOD.b),fpm_(base,p.c,MOD.c));
    }
    int N,M,lim=1,lg,rev[MAXN];
    node Wn[MAXN];
    inline void NTT(node *a,int type){
    	for(int i=0;i<lim;i++)
    		if(i<rev[i])
    			swap(a[i],a[rev[i]]);
    	for(int mid=1;mid<lim;mid<<=1){
    		int len=mid<<1/*n*/;
    		node Wn=fpm(3,(MOD-1)/(LL)len);
    		for(int j=0;j<lim;j+=len){
    			node w=node(1);
    			for(int k=0;k<mid;k++){
    				node x=a[j+k],y=w*a[j+k+mid]%MOD;
    				a[j+k]=(x+y)%MOD;
    				a[j+k+mid]=(x-y+MOD)%MOD;
    				w=w*Wn%MOD;
    			}
    		}
    	}
    	if(type==-1){
    		reverse(a+1,a+lim);
    		node lim_inv=node(inv(lim,MOD.a),inv(lim,MOD.b),inv(lim,MOD.c)); 
    		for(int i=0;i<lim;i++)
    			a[i]=a[i]*lim_inv;
    	}
    }
    node a[MAXN],b[MAXN];
    int main(){
    	scanf("%d%d%lld",&N,&M,&Last_Mod);
    	for(int i=0;i<=N;i++){
    		LL ii;
    		scanf("%lld",&ii);
    		a[i]=node(ii%Last_Mod)%MOD;
    	}
    	for(int i=0;i<=M;i++){
    		LL ii;
    		scanf("%lld",&ii);
    		b[i]=node(ii%Last_Mod)%MOD;
    	}
    	while(lim<=N+M){
    		lim<<=1;
    		lg++;
    	}
    	for(int i=0;i<lim;i++)
    		rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
    	Wn[0]=node(1);
    	for(int i=1;i<lim;i++)
    		Wn[i]=Wn[i-1]*INV;
    	NTT(a,1);
    	NTT(b,1);
    	for(int i=0;i<lim;i++)
    		a[i]=a[i]*b[i]%MOD;
    	NTT(a,-1);
    	for(int i=0;i<=N+M;i++)
    		printf("%lld ",CRT(a[i]));
    	return 0;
    }
    

    例题

    模板题:P4245 【模板】任意模数NTT
    模板题:【模板】多项式求逆(加强版)
  • 相关阅读:
    题解「CF204E Little Elephant and Strings」
    题解「CF1000G Two-Paths」
    消息机制及按钮实效性
    View(视图)——消息机制
    城市线程练习题后续
    城市线程练习题
    View(视图)——对话框之日期对话框和时间对话框文集
    View(视图)——对话框之进度对话框
    删除对话框练习
    拨打电话与发送短信功能
  • 原文地址:https://www.cnblogs.com/guoshaoyang/p/11296140.html
Copyright © 2011-2022 走看看