51Nod 快速傅裏葉變換題集選刷
打開51Nod全部問題頁面,在右邊題目分類中找到快速傅裏葉變換,然後按分值排序,就是本文的題目順序。
1.大數乘法問題
這個……板子就算了吧。
2.美妙的序列問題
長度為n的排列,且滿足從中間任意位置劃分為兩個非空數列後,左邊的最大值>右邊的最小值。問這樣的排列有多少個%998244353。
多組詢問,n,T<=100000。
題解:經過分析可知,不合法的排列一定存在這樣一種劃分:
我們考慮答案=f[i]=i!-不合法排列個數。
形如 2 1 3 4 6 5 這種排列,會有三種劃分方式不合法(1 | 3,3 | 4,4 | 6),直接算階乘會計算重復。
而我們又發現,後兩種劃分,左邊的子串仍是一個不合法的排列(顯然)。
於是我們強制要求左邊的排列是一個合法的排列,即在最左邊統計貢獻,這樣就可以不重不漏了。
得到遞推式顯然:
分治NTT即可,預處理後O(1)回答。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <vector> #include <cmath> #include美妙的序列<map> #include <set> #define LL long long #define FILE "美妙的序列" using namespace std; const int N = 265010; const int Mod = 998244353; const int G = 3; int f[N],rev[N],L,Jc[N],a[N],b[N]; inline int gi(){ int x=0,res=1;char ch=getchar(); while(ch>‘9‘ || ch<‘0‘)res^=ch==‘-‘,ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘)x=x*10+ch-48,ch=getchar(); return res?x:-x; } inline int QPow(int d,int z,int ans=1){ for(;z;z>>=1,d=1ll*d*d%Mod) if(z&1)ans=1ll*ans*d%Mod; return ans; } inline void NTT(int *A,int n,int f){ for(int i=1;i<n;++i) if(i<rev[i])swap(A[i],A[rev[i]]); for(int i=1;i<n;i<<=1){ int z=f*(Mod-1)/(i<<1),gn=QPow(G,(z+Mod-1)%(Mod-1)); for(int j=0;j<n;j+=i<<1){ int g=1,x,y; for(int k=0;k<i;++k,g=1ll*g*gn%Mod){ x=A[j+k];y=1ll*g*A[i+j+k]%Mod; A[j+k]=(x+y)%Mod;A[i+j+k]=(x-y+Mod)%Mod; } } } if(f==1)return;int iv=QPow(n,Mod-2); for(int i=0;i<n;++i)A[i]=1ll*A[i]*iv%Mod; } inline void solve(int l,int r){ if(l==r){f[l]=(Jc[l]-f[l]%Mod+Mod)%Mod;return;} int mid=(l+r)>>1; solve(l,mid); int n,m=r-l+1;L=0; for(n=1;n<=m;n<<=1)L++; for(int i=1;i<n;++i) rev[i]=(rev[i/2]/2)|((i&1)<<(L-1)); for(int i=0;i<n;++i)a[i]=b[i]=0; for(int i=l;i<=mid;++i)a[i-l]=f[i]; for(int i=0;i<m;++i)b[i]=Jc[i]; NTT(a,n,1);NTT(b,n,1); for(int i=0;i<n;++i)a[i]=1ll*a[i]*b[i]%Mod; NTT(a,n,-1); for(int i=mid+1;i<=r;++i)f[i]=(f[i]+a[i-l])%Mod; solve(mid+1,r); } int main(){ //freopen(FILE".in","r",stdin); //freopen(FILE".out","w",stdout); int Case=gi();Jc[0]=1; for(int i=1;i<=100000;++i) Jc[i]=1ll*Jc[i-1]*i%Mod; solve(1,100000); while(Case--)printf("%d\n",f[gi()]); fclose(stdin);fclose(stdout); return 0; }
3.哈希統計問題
給定base,p,求經過經典哈希(ans=(ans*base+a[i])%p;)後哈希值=x的長度<=n的小寫字符串個數%998244353,n,p,base<=50000。
題解:對於長度<=n的問題先不考慮,先考慮恰好為n的。
設f[i][j]為已有i個字母,哈希值為j的串個數,則轉移為: f[i][j] -> f[i+1][(j*base+Ascll[c])%p]。
如果把j*base看成模p意義下的j‘,顯然轉移是一個多項式相乘形式。
常見的套路是:觀察當i為偶數時,f[i/2]是否能直接推出f[i]。
顯然可以,j*base變成j*basei/2就可以了。寫出來是一個卷積的形式,一遍NTT即可。
於是直接暴力遞歸,i為奇數則化為f[i-1]*f[1]繼續暴力,只會做O(log)次。
現在要求<=n的,那麽同樣設pre[i][j]為已有<=i個字母,哈希值為j的串的個數。
轉移:pre[i]*f[j]+pre[j] -> pre[i+j]。
即:選[j+1,i+j]個的和選[1,j]的方案數相加,就是選[1,i+j]個的個數。
剩下的就是一點細節,調試一會兒應該也很好寫出來。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <vector> #include <cmath> #include <map> #include <set> #define LL long long #define FILE "哈希統計" using namespace std; const int N = 265010; const int M = 60; const int Mod = 998244353; const int G = 3; int p,Bs,m,wx; int idf,idpre,f[M][N],pre[M][N],f_vis[N],pre_vis[N]; int n,rev[N],L,a[N],b[N]; inline int gi(){ int x=0,res=1;char ch=getchar(); while(ch>‘9‘ || ch<‘0‘)res^=ch==‘-‘,ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘)x=x*10+ch-48,ch=getchar(); return res?x:-x; } inline int QPow(int d,int z,int Mod,int ans=1){ for(;z;z>>=1,d=1ll*d*d%Mod) if(z&1)ans=1ll*ans*d%Mod; return ans; } inline void NTT(int *A,int f){ 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){ int z=f*(Mod-1)/(i<<1),gn=QPow(G,(z+Mod-1)%(Mod-1),Mod); for(int j=0;j<n;j+=i<<1){ int g=1,x,y; for(int k=0;k<i;++k,g=1ll*g*gn%Mod){ x=A[j+k];y=1ll*g*A[i+j+k]%Mod; A[j+k]=(x+y)%Mod;A[i+j+k]=(x-y+Mod)%Mod; } } } if(f==1)return;int iv=QPow(n,Mod-2,Mod); for(int i=0;i<n;++i)A[i]=1ll*A[i]*iv%Mod; } inline void Mul(int *H,int *g,int *h){ NTT(g,1);NTT(h,1); for(int i=0;i<n;++i) H[i]=1ll*g[i]*h[i]%Mod; NTT(H,-1); for(int i=n-1;i>=p;--i) H[i-p]=(H[i-p]+H[i])%Mod,H[i]=0; } inline int getf(int x){ if(f_vis[x])return f_vis[x]; if(x==1){ ++idf; for(int i=‘a‘;i<=‘z‘;++i) f[idf][i%p]++; return idf; } int id0,id1,len0,len1,pw; if(x&1)id0=getf(len0=x-1),id1=getf(len1=1); else id0=id1=getf(len0=len1=x/2); ++idf;pw=QPow(Bs,len1,p); for(int i=0;i<n;++i)a[i]=0,b[i]=f[id1][i]; for(int i=0;i<p;++i) if(f[id0][i]){ int y=1ll*i*pw%p; a[y]=(a[y]+f[id0][i])%Mod; } Mul(f[idf],a,b); return f_vis[x]=idf; } inline int getpre(int x){ if(pre_vis[x])return pre_vis[x]; if(x==1){ ++idpre;int id=getf(x); for(int i=0;i<n;++i) pre[idpre][i]=f[id][i]; return idpre; } int id0,id1,id2,len0,len1,pw; if(x&1)id0=getpre(len0=x-1),id1=getf(len1=1); else id0=getpre(len0=x/2),id1=getf(len1=x/2); id2=getpre(len1);pw=QPow(Bs,len1,p);++idpre; for(int i=0;i<n;++i)a[i]=0,b[i]=f[id1][i]; for(int i=0;i<p;++i) if(pre[id0][i]){ int y=1ll*i*pw%p; a[y]=(a[y]+pre[id0][i])%Mod; } Mul(pre[idpre],a,b); for(int i=0;i<p;++i) pre[idpre][i]=(pre[idpre][i]+pre[id2][i])%Mod; return pre_vis[x]=idpre; } int main(){ //freopen(FILE".in","r",stdin); //freopen(FILE".out","w",stdout); m=gi();Bs=gi();p=gi();wx=gi(); for(n=1;n<p+p;n<<=1)L++; for(int i=0;i<n;++i) rev[i]=(rev[i/2]/2)|((i&1)<<(L-1)); int id=getpre(m); printf("%d\n",pre[id][wx]); fclose(stdin);fclose(stdout); return 0; }哈希統計
4.乘積之和
給定正整數序列序列A[1...n],有Q次詢問,每次詢問給出k,在A中任選k個數可以得到一個乘積。求所有方案的乘積的總和%100003。n,Q<=50000。
題解:暴力DP很顯然,設f[i][j]表示前i個數選j個數的乘積和,那麽可以直接轉移f[i][j] -> f[i+1][j*A[i+1]%100003]。
用上面那題的套路,f[i/2]是否能推出f[i]?仔細分析後發現是可以的。
發現這也是一個卷積形式!但是這題是有多組詢問的,不能直接暴力遞歸求。
分治•NTT,solve(l,r)表示得到在A[l...r]中選k個的乘積和數組,總復雜度O(nlog2n)。
因為不是費馬質數,卷積上界又只有10^14級別,兩個費馬質數用中國剩余定理合並一下就可以了。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <vector> #include <cmath> #include <map> #include <set> #define LL long long #define FILE "乘積之和" using namespace std; const LL N = 200010; const LL M = 100003; const LL G = 3; LL Q,A[N]; LL f[20][N],rev[N]; LL P[]={998244353,1004535809}; inline LL gi(){ LL x=0,res=1;char ch=getchar(); while(ch>‘9‘ || ch<‘0‘)res^=ch==‘-‘,ch=getchar(); while(ch>=‘0‘&&ch<=‘9‘)x=x*10+ch-48,ch=getchar(); return res?x:-x; } inline LL Mul(LL a,LL b,LL Mod,LL ans=0){ if(Mod<=P[1])return a*b%Mod; for(;b;b>>=1,a=(a+a)%Mod) if(b&1)ans=(ans+a)%Mod; return ans; } inline LL QPow(LL d,LL z,LL Mod,LL ans=1){ for(d=d%Mod,z=z%Mod;z;z>>=1,d=d*d%Mod) if(z&1)ans=ans*d%Mod; return ans; } inline void NTT(LL *A,LL n,LL f,LL Mod){ for(LL i=0;i<n;++i) if(i<rev[i])swap(A[i],A[rev[i]]); for(LL i=1;i<n;i<<=1){ LL z=f*(Mod-1)/(i<<1),gn=QPow(G,(z+Mod-1)%(Mod-1),Mod); for(LL j=0;j<n;j+=i<<1){ LL g=1,x,y; for(LL k=0;k<i;k++,g=1ll*g*gn%Mod){ x=A[j+k];y=1ll*g*A[i+j+k]%Mod; A[j+k]=(x+y)%Mod;A[i+j+k]=(x-y+Mod)%Mod; } } } if(f==1)return;LL iv=QPow(n,Mod-2,Mod); for(LL i=0;i<n;++i)A[i]=1ll*A[i]*iv%Mod; } inline LL CRT(LL r0,LL r1){ LL Mod=1ll*P[0]*P[1]; LL v0=QPow(P[1],P[0]-2,P[0]),v1=QPow(P[0],P[1]-2,P[1]); LL r=(Mul(v0*P[1]%Mod,r0,Mod)+Mul(v1*P[0]%Mod,r1,Mod))%Mod; return r%M; } inline void solve(LL l,LL r,LL dep){ if(l==r){ f[dep][0]=1;f[dep][1]=A[l]%M; return; } LL mid=(l+r)>>1; LL m=r-l+1,n=1,L=0; for(;n<=m;n<<=1)L++; LL a[2][n+10],b[2][n+10]; solve(l,mid,dep+1); for(LL i=0;i<=mid-l+1;++i)a[0][i]=a[1][i]=f[dep+1][i]; for(LL i=mid-l+2;i<n;++i)a[0][i]=a[1][i]=0; solve(mid+1,r,dep+1); for(LL i=0;i<=r-mid;++i)b[0][i]=b[1][i]=f[dep+1][i]; for(LL i=r-mid+1;i<n;++i)b[0][i]=b[1][i]=0; for(LL i=0;i<n;++i) rev[i]=(rev[i/2]/2)|((i&1)<<(L-1)); for(LL t=0;t<2;++t){ NTT(a[t],n,1,P[t]);NTT(b[t],n,1,P[t]); for(LL i=0;i<n;++i)a[t][i]=1ll*a[t][i]*b[t][i]%P[t]; NTT(a[t],n,-1,P[t]); } for(LL i=0;i<=m;++i) f[dep][i]=CRT(a[0][i],a[1][i]); } int main(){ LL n=gi();Q=gi(); for(LL i=1;i<=n;++i)A[i]=gi(); solve(1,n,1); for(LL t=1;t<=Q;++t) printf("%lld\n",f[1][gi()]); fclose(stdin);fclose(stdout); return 0; }乘積之和
5.模糊搜索問題
題意:給定兩個串A,B,字符集大小為4,匹配規則是若A[j-k]~A[j+k]中存在B[i]則算B[i]在A[j]出現,求B在A中出現了多少次,長度<=100000。
比如說k=2的情況。
題解:這種字符串問題用FFT來做的套路似乎都和萬徑人蹤滅差不多?
首先那個k的限制可以用兩遍掃+差分搞定出字符c在A[i]出是否算的上出現,記為g[c][i]。
B在A[i]處開頭,則對於‘A‘、‘T‘、‘C‘、‘G’,,有如下式子:
這樣仍不好做,把式子構造一下:
這就形成了多項式乘法的形式,跑四遍就可以了。
#include <iostream> #include <cstdio> #include <cstdlib> #include <algorithm> #include <cstring> #include <vector> #include <cmath> #include <map> #include <set> #define LL long long #define FILE "模糊搜索" using namespace std; const int N = 530010; const double pi = acos(-1.0); int S,T,K,n,m,L,rev[N],ID[99],cf[N],num[5][N],Ans; struct dob{ double real,imag; dob(){}; dob(double _r,double _i){real=_r;imag=_i;} dob operator +(const dob &a)const{ return (dob){real+a.real,imag+a.imag}; } dob operator -(const dob &a)const{ return (dob){real-a.real,imag-a.imag}; } dob operator *(const dob &a)const{ double r=real*a.real-imag*a.imag; double i=real*a.imag+imag*a.real; return (dob){r,i}; } }a[N],b[N],f[5][N]; char s[N],t[N]; inline int gi(){ int x=0,res=1;char ch=getchar(); while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)res*=-1;ch=getchar();} while(ch<=‘9‘&&ch>=‘0‘)x=x*10+ch-48,ch=getchar(); return x*res; } inline void FFT(dob *A,int f){ 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){ dob wn(cos(pi/i),sin(f*pi/i)),x,y; for(int j=0;j<n;j+=i<<1){ dob w(1,0); for(int k=0;k<i;k++,w=w*wn){ x=A[j+k];y=w*A[i+j+k]; A[j+k]=x+y;A[i+j+k]=x-y; } } } if(f==1)return; for(int i=0;i<n;++i) A[i].real=int(A[i].real/n+0.5); } inline void work(char ch,int sum=0){ for(int i=0;i<n;++i)cf[i]=0; for(int i=1;i<=S;++i) if(s[i]==ch){ cf[max(0,i-K)]++; cf[min(n+1,i+K+1)]--; } for(int i=1;i<n;++i)cf[i]+=cf[i-1]; for(int i=0;i<=S;++i) a[i].real=cf[i]>0,a[i].imag=0; for(int i=S+1;i<n;++i) a[i].real=a[i].imag=0; for(int i=0;i<=T;++i) sum+=b[i].real=t[i]==ch,b[i].imag=0; for(int i=T+1;i<n;++i) b[i].real=b[i].imag=0; reverse(b+1,b+T+1); FFT(a,1);FFT(b,1); for(int i=0,id=ID[ch];i<n;++i) f[id][i]=a[i]*b[i]; FFT(f[ID[ch]],-1); for(int i=1;i<=S;++i) num[ID[ch]][i]=sum==int(f[ID[ch]][i+T].real+0.001); } int main(){ //freopen(FILE".in","r",stdin); //freopen(FILE".out","w",stdout); S=gi();T=gi();K=gi(); for(n=1,m=S+T;n<m;n<<=1)L++; for(int i=1;i<n;++i) rev[i]=(rev[i/2]/2)|((i&1)<<(L-1)); ID[‘A‘]=1;ID[‘T‘]=2;ID[‘C‘]=3;ID[‘G‘]=4; scanf("%s%s",s+1,t+1); work(‘A‘);work(‘T‘);work(‘C‘);work(‘G‘); for(int i=1;i<=S;++i) if(num[1][i] && num[2][i] && num[3][i] && num[4][i]) Ans++; printf("%d\n",Ans); fclose(stdin);fclose(stdout); return 0; }模糊搜索問題
51Nod 快速傅裏葉變換題集選刷