【模板】任意模数多项式乘法

题目描述

给定 \(2\) 个多项式 \(F(x), G(x)\) ,请求出 \(F(x) \times G(x)\)。

系数对 \(p\) 取模,且不保证 \(p\) 可以分解成 \(p = a \cdot 2^k + 1\) 之形式。

题解

可以用快速数论变换NTT算法,关键在于取的那个素数。

由于系数最大为 \(10^5 \times 10^{9+9} = 10^{23}\) 所以可以用 __int128 来存储,取一个可以表示为 \(2^a \times b+1\) 的大素数。

用 Python 来找大素数,取两个随机数 \(a\) 和 \(b\) ,然后用 rabinMiller 来判断是否为素数。

找到了一个大素数:\(2^{73} \times 39+1\)

将这个数的欧拉函数分解质因数,从 \(2\) 开始判断,发现 \(17\) 是它的一个原根。

然后就是 __int128 乘法,先转换成 long double 求出两个数相乘除以模数,再转换回 __int128

代码:

#include<bits/stdc++.h>
#define int long long
#define ll __int128
#define mxn 300000
#define ld long double
#define pb push_back
#define mkp make_pair
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rept(i,a,b) for(int i=a;i<b;++i)
using namespace std;
inline int read(){
	int x=0;char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
const ll md=((ll)1<<73)*39+1;
ll mul(ll x,ll y){
	ll a=(ld)x*y/md;
	ll ans=x*y-a*md;
	ans%=md;
	if(ans<0)ans+=md;
	return ans;
}
ll power(ll x,ll y){
	ll ans=1;
	for(;y;y>>=1){
		if(y&1)ans=mul(ans,x);
		x=mul(x,x);
	}
	return ans;
}
inline ll mod(ll a,ll b){
	return mul(a,power(b,md-2));
}
int n,m,k,s,p,rev[mxn];
ll f[mxn],g[mxn];
void ntt(ll *a,int n,int flag){
	rept(i,0,n)if(i<rev[i])swap(a[i],a[rev[i]]);
	for(int h=1;h<n;h<<=1){
		ll x,y,s=power(17,(md-1)/2/h);
		for(int j=0;j<n;j+=h<<1){
			ll w=1;
			for(int k=j;k<j+h;++k){
				x=a[k],y=mul(w,a[k+h]);
				a[k]=(x+y)%md;
				a[k+h]=(x-y+md)%md;
				w=mul(w,s);
			}
		}
	}
	if(flag==-1){
		ll p=power(n,md-2);
		reverse(a+1,a+n);
		rept(i,0,n)a[i]=mul(a[i],p);
	}
}
signed main(){
	n=read(),m=read(),p=read();
	rep(i,0,n)f[i]=read();
	rep(i,0,m)g[i]=read();
	for(k=0,s=1;s<n+m+1;s<<=1,++k);
	rept(i,0,s)rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));
	ntt(f,s,1);ntt(g,s,1);
	rept(i,0,s)f[i]=mul(f[i],g[i]);
	ntt(f,s,-1);
	rep(i,0,n+m)printf("%lld ",(int)(f[i]%p));
	return 0;
}
posted @ 2024-02-27 22:19  zifanwang  阅读(13)  评论(0编辑  收藏  举报