题目:https://www.luogu.org/problemnew/show/P4245

三模数NTT:

  大概是用3个模数分别做一遍,用中国剩余定理合并。

  前两个合并起来变成一个 long long 的模数,再要和第三个合并的话就爆 long long ,所以可以用一种让两个模数的乘积不出现的方法:https://blog.csdn.net/qq_35950004/article/details/79477797

  x*m1+a1 = -y*m2 + a2  <==>  x*m1+y*m2 = a2-a1  <==>  x*m1 = a2-a1 (mod m2)  <==> x=(a2-a1)*m1^{-1} (mod m2)

  然后根据该博客里的证明,在mod m2意义下算出来的 x 就是真的 x 。这样的话答案就是 x*m1+a1 ,可以在快速乘的过程中对题目中给的模数取模,就不会爆 long long 啦。

  注意输入的 a[ ] 和 b[ ] 不能 ntt( ,0, ) 之后再 ntt( ,1, ) 回来,因为值已经模了刚才那个模数了;所以要多开一些数组。

  注意输入进 mul 里的 a 和 b 应该是正的,不然没法 b>>=1 之类的。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=1e5+;
int m[]={,,};
int n0,n1,mod,len,r[N<<],a[][N<<],b[][N<<],c[][N<<];
ll M=(ll)m[]*m[],d[N<<];
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='') ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
void upd(ll &x,ll md){x>=md?x-=md:;}
void upd(int &x,ll md){x>=md?x-=md:;}
ll mul(ll a,ll b,ll md)
{
a%=md; b%=md;//
ll ret=;while(b){if(b&1ll)ret+=a,upd(ret,md);a+=a;upd(a,md);b>>=1ll;}return ret;
}
ll pw(ll x,ll k,ll md)
{ll ret=;while(k){if(k&1ll)ret=mul(ret,x,md);x=mul(x,x,md);k>>=1ll;}return ret;}
void ntt(int *a,bool fx,int md)
{
for(int i=;i<len;i++)
if(i<r[i])swap(a[i],a[r[i]]);
for(int R=;R<=len;R<<=)
{
int m=R>>;
int Wn=pw(,(md-)/R,md);
fx?Wn=pw(Wn,md-,md):;
for(int i=;i<len;i+=R)
for(int j=,w=;j<m;j++,w=(ll)w*Wn%md)
{
int tmp=(ll)w*a[i+m+j]%md;
a[i+m+j]=a[i+j]+md-tmp; upd(a[i+m+j],md);
a[i+j]=a[i+j]+tmp; upd(a[i+j],md);
}
}
if(!fx)return; int inv=pw(len,md-,md);
for(int i=;i<len;i++) a[i]=(ll)a[i]*inv%md;
}
int main()
{
n0=rdn()+; n1=rdn()+; mod=rdn();
for(int i=;i<n0;i++)a[][i]=a[][i]=a[][i]=rdn();
for(int i=;i<n1;i++)b[][i]=b[][i]=b[][i]=rdn();
for(len=;len<=n0+n1;len<<=);
for(int i=;i<len;i++)r[i]=(r[i>>]>>)+((i&)?len>>:);
for(int i=;i<;i++)//don't ntt(a,1,m[i]) for it can't return(already mod)
{
ntt(a[i],,m[i]); ntt(b[i],,m[i]);
for(int j=;j<len;j++)c[i][j]=(ll)a[i][j]*b[i][j]%m[i];
ntt(c[i],,m[i]);
} ll inv=pw(m[],m[]-,m[]),t;
for(int i=,lm=n0+n1-;i<lm;i++)
{
t=mul((c[][i]-c[][i])%m[]+m[],inv,m[]);
d[i]=(mul(t,m[],M)+c[][i])%M;
}
inv=pw(M,m[]-,m[]);
for(int i=,lm=n0+n1-;i<lm;i++)
{
t=mul((c[][i]-d[i])%m[]+m[],inv,m[]);
d[i]=(mul(t,M,mod)+d[i])%mod;
printf("%lld ",d[i]);
}
puts(""); return ;
}

拆系数FFT:

  参考材料:https://blog.csdn.net/lvzelong2014/article/details/80156989

  因为数值最大可能 10^(9+9+5) ,double 的精度可能很不好。所以把 a[ i ] 拆成 k*m + b ,其中 m 大约是 sqrt(mod) ;

  这样的话2个多项式变成了4个多项式,卷积的时候只卷积 k 和 b ,不用带上 m ,数值最大就是 sqrt(mod) 级别的,精度就能行了。

  但 ( k1 + b1 )*( k2 + b2 ) = k1*k2 + b1*k2 + k1*b2 + b1*b2 ,需要4次卷积,做8次DFT(似乎可以弄成7次),太慢了。

  所以就像那个博客里说的那样:

  令 \( P(x) = A(x)+i*B(x) , Q(x) = A(x)-i*B(x) \) (写的时候就是把 a[ i ] 放在 p[ i ] 的实部、把 b[ i ] 放在 p[ i ] 的虚部)

  则 \( Q(w_{n}^{k}) = conj(P(w_{n}^{-k})) \)(这里已经代入了点值。即把 P DFT之后,可以根据它得到 Q 的点值)(证明见那个博客)

  然后由 P(x) 和 Q(x) 的定义,得

  \( A(w_{n}^{k}) = \frac{P(w_{n}^{k})+Q(w_{n}^{k})}{2} \)(点值)

  \( i*B(w_{n}^{k}) = \frac{P(w_{n}^{k})-Q(w_{n}^{k})}{2} \) 即 \( B(w_{n}^{k}) = i*\frac{Q(w_{n}^{k})-P(w_{n}^{k})}{2} \)

  把向量写成 ( 实部+ i * 虚部 ) 的样子,就能把 i 代进去,从而得到 A(x) 和 B(x) 的点值向量。这种方法利用 P(x) 和 Q(x) 的关联,只 DFT 了一下P(x),就求出了A(x)和B(x)两个多项式的点值!

  所以 DFT 两次,求出 k1、b1、k2、b2 的点值。相乘一番,得到 k1*k2 、b1*k2 、k1*b2 、b1*b2 的点值。

  考虑 iDFT 回去。还是设 \( P(w_{n}^{k}) = A(w_{n}^{k}) + i*B(w_{n}^{k})\)(点值),写的时候还是把向量写开,就能把 i 代进去,得到 \( P(w_{n}^{k}) \) 的向量。

  这时的 \( P(w_{n}^{k}) \) 是一组点值, iDFT 回去后直接是系数的 \( A(x) + i*B(x) \) !也就是 iDFT 后 p[ i ] 的实部是 A(x) 的系数 a[ i ] ,虚部是 b[ i ] 。

    这个的证明可以看那个博客,大概就是如果系数是( A 的系数 + B 的系数 )的话,点值也应该是 ( A 的点值 + B 的点值 );倒着推一下得到点值是 ( A 的点值 + B 的点值 ) 的话,iDFT 回去的系数也应该是 ( A 的系数 + B 的系数 ) 。

  这里的A(x)和B(x)就是 k1*k2 、b1*k2 、k1*b2 、b1*b2 中的两个。两次 iDFT 后就得到了这4个东西的系数表达!

  然后统计答案就行了。注意乘上 m2 或者 m 。

  这道题得开 long double 。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define db long double
#define ll long long
using namespace std;
const int N=1e5+,M=(<<)+; const db pi=acos(-);
int mod,a[N],b[N],len,r[M],ans[M];
struct cpl{db x,y;}pa[M],pb[M],Ta[M],Tb[M],Tc[M],Td[M],I;
cpl operator+ (cpl a,cpl b){return (cpl){a.x+b.x,a.y+b.y};}
cpl operator- (cpl a,cpl b){return (cpl){a.x-b.x,a.y-b.y};}
cpl operator* (cpl a,cpl b){return (cpl){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};}
cpl cnj(cpl a){return (cpl){a.x,-a.y};}
void upd(int &x){x>=mod?x-=mod:;}
int rdn()
{
int ret=;bool fx=;char ch=getchar();
while(ch>''||ch<''){if(ch=='-')fx=;ch=getchar();}
while(ch>=''&&ch<='') ret=ret*+ch-'',ch=getchar();
return fx?ret:-ret;
}
void fft(cpl *a,bool fx)
{
for(int i=;i<len;i++)
if(i<r[i])swap(a[i],a[r[i]]);
for(int R=;R<=len;R<<=)
{
int m=R>>;
cpl Wn=(cpl){ cos(pi/m),fx?-sin(pi/m):sin(pi/m) };
for(int i=;i<len;i+=R)
{
cpl w=I;
for(int j=;j<m;j++,w=w*Wn)
{
cpl x=a[i+j],y=w*a[i+m+j];
a[i+j]=x+y; a[i+m+j]=x-y;
}
}
}
if(!fx)return;
for(int i=;i<len;i++)a[i].x/=len,a[i].y/=len;//y!! for use y
}
void solve(int n,int *a,int m,int *b)
{
int bin=(<<)-; cpl ta,tb,tc,td; for(int i=;i<=n;i++)pa[i]=(cpl){a[i]>>,a[i]&bin};//pa=A+iB(a=km+b,A=k,B=b)
for(int i=;i<=m;i++)pb[i]=(cpl){b[i]>>,b[i]&bin};//<=m!!!
for(len=;len<=n+m;len<<=);
for(int i=;i<len;i++)r[i]=(r[i>>]>>)+((i&)?len>>:);
fft(pa,); fft(pb,);
pa[len]=pa[]; pb[len]=pb[];
for(int i=,j=len;i<len;i++,j--)//j=-i+len(if i=0 then j=0),qa[i]=cnj(pa[j])
{
//ta:point value of A
ta=(pa[i]+cnj(pa[j]))*(cpl){0.5,};//ta={(pa[i].x+pa[j].x)/2,(pa[i].y+pa[j].y)/2}
tb=(pa[i]-cnj(pa[j]))*(cpl){,-0.5};//tb=i(Q-P)/2=i*((Qx-Px)+i(Qy-Py))/2={(Py-Qy)/2,(Qx-Px)/2}
tc=(pb[i]+cnj(pb[j]))*(cpl){0.5,};
td=(pb[i]-cnj(pb[j]))*(cpl){,-0.5};
Ta[i]=ta*tc;Tb[i]=ta*td;Tc[i]=tb*tc;Td[i]=tb*td;
}
pa[len]=pb[len]=(cpl){,};
for(int i=;i<len;i++)pa[i]=Ta[i]+Tb[i]*(cpl){,};//pa=Ta+i*Tb={Tax-Tby,Tay+Tbx}
for(int i=;i<len;i++)pb[i]=Tc[i]+Td[i]*(cpl){,};
fft(pa,); fft(pb,);//pa.x=Ta,pa.y=Tb
for(int i=,j=n+m;i<=j;i++)
{
int Da=(ll)(pa[i].x+0.5)%mod;
int Db=(ll)(pa[i].y+0.5)%mod;
int Dc=(ll)(pb[i].x+0.5)%mod;
int Dd=(ll)(pb[i].y+0.5)%mod;
ans[i]=(((ll)Da<<) + ((ll)(Db+Dc)<<) + Dd)%mod+mod; upd(ans[i]);
}
}
int main()
{
int n,m; I.x=;
n=rdn();m=rdn();mod=rdn();
for(int i=;i<=n;i++)a[i]=rdn()%mod+mod,upd(a[i]);
for(int i=;i<=m;i++)b[i]=rdn()%mod+mod,upd(b[i]);
solve(n,a,m,b);
for(int i=,j=n+m;i<=j;i++)printf("%d ",ans[i]);puts("");
return ;
}

洛谷 4245 【模板】任意模数NTT——三模数NTT / 拆系数FFT的更多相关文章

  1. 洛谷.4245.[模板]任意模数NTT(MTT/三模数NTT)

    题目链接 三模数\(NTT\): 就是多模数\(NTT\)最后\(CRT\)一下...下面两篇讲的都挺明白的. https://blog.csdn.net/kscla/article/details/ ...

  2. 洛谷 P4245 [模板]任意模数NTT —— 三模数NTT / 拆系数FFT(MTT)

    题目:https://www.luogu.org/problemnew/show/P4245 用三模数NTT做,需要注意时间和细节: 注意各种地方要取模!传入 upt() 里面的数一定要不超过2倍 m ...

  3. 洛谷P3373 [模板]线段树 2(区间增减.乘 区间求和)

    To 洛谷.3373 [模板]线段树2 题目描述 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.将某区间每一个数乘上x 3.求出某区间每一个数的和 输入输出格式 输入格 ...

  4. 【洛谷5月月赛】玩游戏(NTT,生成函数)

    [洛谷5月月赛]玩游戏(NTT,生成函数) 题面 Luogu 题解 看一下要求的是什么东西 \((a_x+b_y)^i\)的期望.期望显然是所有答案和的平均数. 所以求出所有的答案就在乘一个逆元就好了 ...

  5. 拆系数FFT(任意模数FFT)

    拆系数FFT 对于任意模数 \(mod\) 设\(m=\sqrt {mod}\) 把多项式\(A(x)\)和\(B(x)\)的系数都拆成\(a\times m+b\)的形式,时\(a, b\)都小于\ ...

  6. 洛谷4245:【模板】任意模数NTT——题解

    https://www.luogu.org/problemnew/show/P4245 给两个多项式,求其乘积,每个系数对p取模. 参考: 代码与部分理解参考https://www.luogu.org ...

  7. 洛谷.3803.[模板]多项式乘法(NTT)

    题目链接:洛谷.LOJ. 为什么和那些差那么多啊.. 在这里记一下原根 Definition 阶 若\(a,p\)互质,且\(p>1\),我们称使\(a^n\equiv 1\ (mod\ p)\ ...

  8. 洛谷 P5206 - [WC2019]数树(集合反演+NTT)

    洛谷题面传送门 神仙多项式+组合数学题,不过还是被我自己想出来了( 首先对于两棵树 \(E_1,E_2\) 而言,为它们填上 \(1\sim y\) 使其合法的方案数显然是 \(y\) 的 \(E_1 ...

  9. LCT总结——概念篇+洛谷P3690[模板]Link Cut Tree(动态树)(LCT,Splay)

    为了优化体验(其实是强迫症),蒟蒻把总结拆成了两篇,方便不同学习阶段的Dalao们切换. LCT总结--应用篇戳这里 概念.性质简述 首先介绍一下链剖分的概念(感谢laofu的讲课) 链剖分,是指一类 ...

随机推荐

  1. Adding Flexcan driver support on Kernel

    Adding Flexcan driver support on Kernel On kernel menuconfig, add the following items: [*] Networkin ...

  2. php面向对象之克隆对象

    在前面的PHP面向对象之对象和引用,我们试图以"$b=$a"的方式复制对象以传递对象的值(内容),结果却是传递对象的地址,在结尾为了解决复制对象这个问题,提到了克隆的方法.接下来讲 ...

  3. Django源码剖析

    一.Django底层剖析之一次请求到响应的整个流程 As we all know,所有的Web应用,其本质上其实就是一个socket服务端,而用户的浏览器就是一个socket客户端 #!/usr/bi ...

  4. iOS应用适配IPV6

    网络收集,连接如下: 针对苹果iOS最新审核要求为应用兼容IPv6 iOS应用支持IPV6,就那点事儿 iOS 适配iPV6的修改(AF及其他第三方库)

  5. Whitewidow:SQL 漏洞自动扫描工具

    Whitewidow 是一个开源的 SQL 漏洞自动扫描器,可用通过文件列表运行,或者从 Google 爬取并发现有潜在漏洞的网站. 这个工具支持自动格式化文件.随机用户代理.IP 地址.服务器信息. ...

  6. Windows下MetaMap工具安装

    Windows下MetaMap工具安装 一.Main MetaMap安装 Prerequisties 12G磁盘空间 JAVA6 or newer JRE or SDK installed Downl ...

  7. spring mvc 官方下载

    1.进入https://spring.io 2.选择projects选项卡 3.点击spring frawewoek 4.选择右中方的Reference 5.选择2.3章节 6.点击 Distribu ...

  8. App测试经验分享之登录注册

    要诀 另外自己总结了一些要诀,仅供参考: 1)快:快速操作,营造冲突的场景,例如加载过程中返回键交互,快速点击登录按钮,快速切换菜单项,快速多次上下拉刷新 2)变:手机横竖屏.手机切换语言.手机调整字 ...

  9. numpy加权平均

    import numpy as np a = np.arange(15).reshape(3,5) a array([[ 0, 1, 2, 3, 4],    [ 5, 6, 7, 8, 9],   ...

  10. Git在mac中和远程仓库建立连接

    1.下载git http://git-scm.com/download/ 2. 安装git 按照文字提示即可 3. 验证是否成功,输入命令行.输出git版本表示git安装成功. git --versi ...