【數論】FFT,NTT,FWT
FFT-快速傅立葉變換
一,時間複雜度
時間複雜度: \(\mathcal{O(nlogn)}\)
二,限制
- 計算過程(即 多項式相乘過程)不能取模;
- 常數較大;
- 會存在精度差。
三,模板
計算多項式 \(C=A\times B\), 其中 多項式 \(A\) 的長度是 \(n+1\), 多項式 \(B\) 的長度是 \(m+1\) ,得到多項式 \(C\) 的長度是 \(n+m+1\)。
#include <bits/stdc++.h> using namespace std; const int maxn=2e6+5; const double PI=acos(-1); struct comp{ double x,y; comp operator +(comp b){return comp{x+b.x,y+b.y};} comp operator -(comp b){return comp{x-b.x,y-b.y};} comp operator *(comp b){return comp{x*b.x-y*b.y,x*b.y+y*b.x};} }; int up,L,R[maxn<<1]; void fft_init(int n,int m){ up=1;L=0; while(up<n+m+2)up<<=1,L++; for(int i=0;i<up;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(L-1)); } comp a[maxn<<1],b[maxn<<1],c[maxn<<1]; void fft(comp a[],int type) { for(int i=0;i<up;i++) if(i<R[i])swap(a[i],a[R[i]]); for(int mid=1;mid<up;mid<<=1) { comp wn=comp{cos(PI/mid),type*sin(PI/mid)}; for(int r=mid<<1,j=0;j<up;j+=r) { comp w{1,0}; for(int k=0;k<mid;k++,w=w*wn) { comp x=a[j+k],y=w*a[j+mid+k]; a[j+k]=x+y; a[j+mid+k]=x-y; } } } } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=0;i<=n;i++)scanf("%lf",&a[i].x); for(int i=0;i<=m;i++)scanf("%lf",&b[i].x); fft_init(n,m); fft(a,1);fft(b,1); for(int i=0;i<=up;i++)c[i]=a[i]*b[i]; fft(c,-1); for(int i=0;i<=n+m;i++)printf("%d ",(int)(c[i].x/up+0.5)); }
NTT-快速數論變換
一, 時間複雜度
時間複雜度: \(\mathcal{O(nlogn)}\)
二,限制
-
係數必須是整數;
-
係數取模時, 模數有限制, 需要知道模數的原根。
-
常見模數對應原根:
998244353 \(\to\) 3
100000009 \(\to\) 5
-
三,模板
計算多項式 \(C=A\times B\), 其中 多項式 \(A\) 的長度是 \(n+1\), 多項式 \(B\) 的長度是 \(m+1\) ,得到多項式 \(C\) 的長度是 \(n+m+1\)。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e6+5; const int mod=998244353; const int g=3; const int gi=332748118;//invg ll kpow(ll a,ll b){ ll ans=1; while(b){ if(b&1)ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } int up,L,R[maxn<<1]; void ntt_init(int n,int m){ up=1;L=0; while(up<n+m+2)up<<=1,L++; for(int i=0;i<up;i++) R[i]=(R[i>>1]>>1)|((i&1)<<(L-1)); } int a[maxn<<1],b[maxn<<1],c[maxn<<1]; void ntt(int a[],int type) { for(int i=0;i<up;i++) if(i<R[i])swap(a[i],a[R[i]]); for(int mid=1;mid<up;mid<<=1){ ll wn=kpow(type==1?g:gi,(mod-1)/(mid<<1)); for(int r=mid<<1,j=0;j<up;j+=r) { ll w=1; for(int k=0;k<mid;k++,w=w*wn%mod) { int 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; } } } } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=0;i<=n;i++)scanf("%d",&a[i]); for(int i=0;i<=m;i++)scanf("%d",&b[i]); ntt_init(n,m); ntt(a,1);ntt(b,1); for(int i=0;i<up;i++)c[i]=1ll*a[i]*b[i]%mod; ntt(c,-1); int inv=kpow(up,mod-2); for(int i=0;i<=n+m;i++)printf("%lld ",1ll*c[i]*inv%mod); }
FWT-快速沃爾什變換
一,作用
FFT可以解決多項式卷積, 即:
\[C_k=\sum_{k=i+j}\,A_i*B_j \]
FWT可以解決 或/與/異或 卷積, 即:
\[C_k=\sum_{k=i|j}\,A_i*B_j\\ C_k=\sum_{k=i\&j}\,A_i*B_j\\ C_k=\sum_{k=i\bigotimes j}\,A_i*B_j \]
二,時間複雜度
時間複雜度: \(\mathcal{O}(nlogn)\)
三,做法
1, 將 \(A\) 轉換成 \(FWT[A]\) , 將 \(B\) 轉換成 \(FWT[B]\)
2, 計算
\[FWT[C][i]=FWT[A][i]*FWT[B][i] \]
3, 將 \(FWT[C]\) 轉換成 \(C\) .
四,模板
\(code:\)
void FWT_or(long long a[],int len)//A -> FAT[A]
{
for(int mid=2;mid<=len;mid<<=1)
for(int i=0;i<len;i+=mid)
for(int j=i;j<i+(mid>>1);j++)a[j+(mid>>1)]+=a[j];
}
void IFWT_or(long long a[],int len)// FAT[A] -> A
{
for(int mid=2;mid<=len;mid<<=1)
for(int i=0;i<len;i+=mid)
for(int j=i;j<i+(mid>>1);j++)a[j+(mid>>1)]-=a[j];
}
void FWT_and(long long a[],int len)
{
for(int mid=2;mid<=len;mid<<=1)
for(int i=0;i<len;i+=mid)
for(int j=i;j<i+(mid>>1);j++)a[j]+=a[j+(mid>>1)];
}
void IFWT_and(long long a[],int len)
{
for(int mid=2;mid<=len;mid<<=1)
for(int i=0;i<len;i+=mid)
for(int j=i;j<i+(mid>>1);j++)a[j]-=a[j+(mid>>1)];
}
void FWT_xor(long long a[],int len)
{
for(int mid=2;mid<=len;mid<<=1)
for(int i=0;i<len;i+=mid)
for(int j=i;j<i+(mid>>1);j++)
{
long long x=a[j],y=a[j+(mid>>1)];
a[j]=x+y,a[j+(mid>>1)]=x-y;
}
}
inline void IFWT_xor(long long a[],int len)
{
for(int mid=2;mid<=len;mid<<=1)
for(int i=0;i<len;i+=mid)
for(int j=i;j<i+(mid>>1);j++)
{
long long x=a[j],y=a[j+(mid>>1)];
a[j]=(x+y)>>1,a[j+(mid>>1)]=(x-y)>>1;
}
}
五,例題
題意:
給定長度為 \(n\) 的陣列 \(A\) ,其中 \(1\le n\le 2\times 10^5,\,0\le A_i<2^{18}\) 。
對於所有的 \(1\le i\le n\) ,要求:
從陣列 \(A\) 中取出 \(i\) 個數(可重複取同一個數) , 計算出 \(a_1\bigotimes a_2\bigotimes...\bigotimes a_i\) 的值。
題解:
當 \(i> 19\) 之後 , 有 \(ans_i=ans_{i-2}\) 。
只需用異或卷積求出前 \(19\) 個答案。
程式碼:
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;
const int maxn=1e6+5;
void read(int&x)
{
char c;
while(!isdigit(c=getchar()));x=c-'0';
while(isdigit(c=getchar()))x=(x<<3)+(x<<1)+c-'0';
}
void FWT_xor(int a[],int n){
for(int mid=2;mid<=n;mid<<=1)
for(int i=0;i<n;i+=mid)
for(int j=i,x,y;j<i+(mid>>1);j++)
{
x=a[j];y=a[j+(mid>>1)];
a[j]=x+y;a[j+(mid>>1)]=x-y;
}
}
void IFWT_xor(int a[],int n){
for(int mid=2;mid<=n;mid<<=1)
for(int i=0;i<n;i+=mid)
for(int j=i,x,y;j<i+(mid>>1);j++)
{
x=a[j];y=a[j+(mid>>1)];
a[j]=(x+y)>>1;a[j+(mid>>1)]=(x-y)>>1;
}
}
int a[maxn],b[maxn],c[maxn];
int ans[maxn];
int main()
{
int n,up,ed=1<<18;
read(n);
for(int i=1,x;i<=n;i++){
read(x);
a[x]=1;ans[1]=max(ans[1],x);
}
up=min(n,19);
FWT_xor(a,ed);
for(int i=0;i<ed;i++)b[i]=a[i];
for(int i=2;i<=up;i++){
for(int j=0;j<ed;j++)
c[j]=a[j]*b[j];
IFWT_xor(c,ed);
for(int j=0;j<ed;j++)
if(c[j])ans[i]=j,c[j]=1;
FWT_xor(c,ed);
for(int j=0;j<ed;j++)b[j]=c[j];
}
for(int i=up+1;i<=n;i++)ans[i]=ans[i-2];
for(int i=1;i<=n;i++)printf("%d ",ans[i]);putchar(10);
}