FFT、NTT、FWT、FST專場
Sum the Fibonacci
計算所有滿足條件的五元組的貢獻f
題解:
直接上FWT
誒,這個條件3怎麼搞啊
看了一下vfleaking的論文
其實就是FST,FST就是把原集合形式冪級數按照集合大小拆分出來,形成logn個佔位多項式
然後對這些佔位多項式先進行FMT(FWT的or變換)或FWT(FWT的xor變換)
佔位多項式之間就可以暴力卷積,反正只有logn個
最後取出滿足條件的多項式係數疊加進答案
至於f怎麼計算,就可以先卷出a|b,a^b的答案乘上對應的係數,然後再進行&卷積即可
所以總複雜度O(n*logn^2)
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 200005
const int mod=1000000007;
const int inv2=500000004;
int A[N],B[N],C[N],con[N],fib[N];
int f[18][N],tmp[N];
void FMT(int a[],int len,int flg)
{
for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j;k<i+j;k++)a[k+i]=(a[k+i]+flg*a[k])%mod;
}
void FAT(int a[],int len,int flg)//hh AND-FWT
{
for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j;k<i+j;k++)a[k]=(a[k]+flg*a[i+k])%mod;
}
void FWT(int a[],int len,int flg)
{
for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j;k<i+j;k++){
int u=a[k],v=a[k+i];a[k]=(u+v)%mod;a[k+i]=(u-v)%mod;
if(flg==-1)a[k]=1ll*inv2*a[k]%mod,a[k+i]=1ll*inv2*a[k+i]%mod;
}
}
void FST(int a[],int len,int cnt)// a*a
{
for(int i=0;i<len;i++) f[con[i]][i]=a[i],a[i]=0;
for(int i=0;i<=cnt;i++)FMT(f[i],len,1);// real-FST
for(int i=0;i<=cnt;i++){
for(int s=0;s<len;s++)tmp[s]=0;
for(int s=0;s<len;s++)
for(int j=0;j<=i;j++)//zhan wei duo xiang shi juan ji
tmp[s]=(1ll*tmp[s]+1ll*f[i-j][s]*f[j][s])%mod;
FMT(tmp,len,-1);
for(int s=0;s<len;s++)if(con[s]==i)a[s]=(a[s]+tmp[s])%mod;
}
}
int main()
{
int n,i,x,len=1,cnt=0,mx=0,ans=0;
n=gi();
fib[1]=con[1]=1;
for(i=2;i<=200000;i++)fib[i]=(fib[i-1]+fib[i-2])%mod,con[i]=con[i>>1]+(i&1);
for(i=1;i<=n;i++){x=gi();mx=max(mx,x);A[x]++;B[x]++;C[x]++;}
while(len<=mx)len<<=1,cnt++;
FST(A,len,cnt);//a*a
FWT(C,len,1);for(i=0;i<len;i++)C[i]=1ll*C[i]*C[i]%mod;FWT(C,len,-1);//c*c
for(i=0;i<len;i++){
A[i]=1ll*fib[i]*A[i]%mod;
B[i]=1ll*fib[i]*B[i]%mod;
C[i]=1ll*fib[i]*C[i]%mod;
}
FAT(A,len,1);FAT(B,len,1);FAT(C,len,1);
for(i=0;i<len;i++)A[i]=1ll*A[i]*B[i]%mod*C[i]%mod;//a*b*c
FAT(A,len,-1);
for(i=1;i<len;i<<=1)ans=(ans+A[i])%mod;
printf("%d",(ans+mod)%mod);
}
Hard Nim
Claris和NanoApe在玩石子游戲,他們有n堆石子,規則如下:
1. Claris和NanoApe兩個人輪流拿石子,Claris先拿。
2. 每次只能從一堆中取若干個,可將一堆全取走,但不可不取,拿到最後1顆石子的人獲勝。
不同的初始局面,決定了最終的獲勝者,有些局面下先拿的Claris會贏,其餘的局面Claris會負。
Claris很好奇,如果這n堆石子滿足每堆石子的初始數量是不超過m的質數,而且他們都會按照最優策略玩遊戲,那麼NanoApe能獲勝的局面有多少種。
由於答案可能很大,你只需要給出答案對10^9+7取模的值。
輸入文件包含多組數據,以EOF爲結尾。
對於每組數據:
共一行兩個正整數n和m。
每組數據有1<=n<=10^9, 2<=m<=50000。
不超過80組數據。
題解:FWT+快速冪
代碼:(爲什麼次次都忘記+mod再%mod啊。。。)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 70005
int prime[N],tot;
bool vis[N];
void shai()
{
int i,j,n=50000;
vis[1]=1;
for(i=2;i<=n;i++){
if(!vis[i])prime[++tot]=i;
for(j=1;j<=tot;j++){
int tmp=i*prime[j];
if(tmp>n)break;
vis[tmp]=1;
if(i%prime[j]==0)break;
}
}
}
const int mod=1000000007;
const int inv2=500000004;
int A[N];
void FWT(int a[],int len,int flg)
{
for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j;k<i+j;k++){
int u=a[k],v=a[k+i];a[k]=(u+v)%mod;a[k+i]=(u-v)%mod;
if(flg==-1)a[k]=1ll*a[k]*inv2%mod,a[k+i]=1ll*a[k+i]*inv2%mod;
}
}
int ksm(int x,int y)
{
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
y>>=1;x=1ll*x*x%mod;
}
return ret;
}
int main()
{
int n,m,i,len;
shai();
while(~scanf("%d%d",&n,&m)){
memset(A,0,sizeof(A));len=1;
for(i=1;i<=tot;i++){if(prime[i]>m)break;A[prime[i]]++;}
while(len<=m)len<<=1;
FWT(A,len,1);for(i=0;i<len;i++)A[i]=ksm(A[i],n);FWT(A,len,-1);
printf("%d\n",(A[0]+mod)%mod);
}
}
[HAOI2015]按位或
剛開始你有一個數字0,每一秒鐘你會隨機選擇一個[0,2^n-1]的數字,與你手上的數字進行或(c++,c的|,pascal
的or)操作。選擇數字i的概率是p[i]。保證0<=p[i]<=1,Σp[i]=1問期望多少秒後,你手上的數字變成2^n-1。
Input
第一行輸入n表示n個元素,第二行輸入2^n個數,第i個數表示選到i-1的概率
Output
僅輸出一個數表示答案,絕對誤差或相對誤差不超過1e-6即可算通過。如果無解則要輸出INF
Sample Input
2
0.25 0.25 0.25 0.25
Sample Output
2.6666666667
Hint
對於100%的數據,n<=20
題解:
可以發現這是概率的集合形式冪級數的OR卷積的正無窮次冪全集項對應的期望
假設最後一個集合S在k步之前到達它的概率爲Ps[k]
那麼到它的期望就是Σk*(Ps[k]-Ps[k-1])
展開一下就是-Ps[1]-Ps[2]-Ps[3]-……
其實就是我們要計算的概率集合形式冪級數的冪和
我們可以等比數列求和得到-1/(1-Ps[1])
如果我們已經把概率集合形式冪級數進行了FMT,就可以單獨對每一項進行這樣的計算,再IFMT回去即可
代碼:(被卡精度了,WA了好幾次。。。)
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1148576
const double eps=1e-8;
double A[N];
void FMT(double a[],int len,double flg)
{
for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j;k<i+j;k++)a[k+i]=a[k+i]+a[k]*flg;
}
int main()
{
int n,i,len;
scanf("%d",&n);len=1<<n;
for(i=0;i<len;i++)scanf("%lf",&A[i]);
FMT(A,len,1);
for(i=0;i<len;i++){
if(1-A[i]<eps)A[i]=0;
else A[i]=-1/(1-A[i]);
}
FMT(A,len,-1);
if(A[len-1]<eps)printf("INF\n");
else printf("%.10f\n",A[len-1]);
}
Tree Cutting
題解:FWT優化樹型DP
代碼:(由於IFWT次數較多,所以可以預處理所有的len的逆元,在最後退出的時候乘)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 2105
int f[N][N],n,m,len;
int fir[N],to[N],nxt[N],cnt;
void adde(int a,int b)
{
to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;
}
const int mod=1000000007;
const int inv2=500000004;
int ans[N],inv[N];
void FWT(int a[],int flg)
{
for(int i=1;i<len;i<<=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j;k<i+j;k++){
int u=a[k],v=a[k+i];
a[k]=u+v;a[k+i]=u-v;
if(a[k]>=mod)a[k]-=mod;
if(a[k+i]<0)a[k+i]+=mod;
//if(flg==-1)a[k]=1ll*a[k]*inv2%mod,a[k+i]=1ll*a[k+i]*inv2%mod;
}
if(flg==-1){
for(int i=0;i<len;i++)
a[i]=1ll*a[i]*inv[len]%mod;
}
/*for(int i=0;i<len;i++)
printf("%d ",a[i]);
printf("\n");*/
}
void dfs(int u,int ff)
{
FWT(f[u],1);
for(int v,p=fir[u];p;p=nxt[p]){
if((v=to[p])!=ff){
dfs(v,u);
FWT(f[v],1);
for(int i=0;i<len;i++)f[u][i]=1ll*f[u][i]*f[v][i]%mod;
}
}
FWT(f[u],-1);
for(int i=0;i<m;i++)ans[i]=(ans[i]+f[u][i])%mod;
f[u][0]++;if(f[u][0]>=mod)f[u][0]-=mod;
}
int main()
{
int T,i,x,u,v;
inv[1]=1;inv[2]=inv2;for(i=4;i<=2048;i<<=1)inv[i]=1ll*inv[i>>1]*inv2%mod;
scanf("%d",&T);
while(T--){
memset(fir,0,sizeof(fir));cnt=0;
memset(ans,0,sizeof(ans));
scanf("%d%d",&n,&m);len=1;
while(len<=m)len<<=1;
for(i=1;i<=n;i++){
scanf("%d",&x);memset(f[i],0,sizeof(f[i]));
f[i][x]=1;
}
for(i=1;i<n;i++){
scanf("%d%d",&u,&v);
adde(u,v);
}
dfs(1,0);
printf("%d",(ans[0]+mod)%mod);
for(i=1;i<m;i++)printf(" %d",(ans[i]+mod)%mod);
printf("\n");
}
}
Placing Rooks
題解:
可以直接容斥
假設每行都放且只放了一個(每列只放一個的情況可以直接*2)
那麼要形成k對沖突,就必須把某幾個放到同一列,也就是隻會由n-k列有棋子
我們可以從n列中選出n-k列,讓棋子可以恰好放滿這n-k列
但是恰好並不好計算,我們可以考慮容斥
枚舉這n-k列中有i列爲空
那麼i列全不爲空的方案就是
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200005
const int mod=998244353;
int n,fac[N],inv[N];long long k;
int ksm(int x,int y)
{
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
y>>=1;x=1ll*x*x%mod;
}
return ret;
}
void shai()
{
int i;
fac[0]=fac[1]=inv[0]=inv[1]=1;
for(i=2;i<=n;i++)fac[i]=1ll*fac[i-1]*i%mod;
inv[n]=ksm(fac[n],mod-2);
for(i=n;i>=2;i--)inv[i-1]=1ll*inv[i]*i%mod;
}
int C(int x,int y)
{
return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int main()
{
int i,ans=0,tmp;
scanf("%d%lld",&n,&k);
shai();
if(k>=n){printf("0");return 0;}
if(k==0){printf("%d",fac[n]);return 0;}
for(i=0;i<n-k;i++){
tmp=1ll*C(n-k,i)*ksm(n-k-i,n)%mod;
if(i&1)ans=(ans+mod-tmp)%mod;
else ans=(ans+tmp)%mod;
}
ans=2ll*C(n,n-k)*ans%mod;
printf("%d",ans);
}
Substring Search
題解:利用NTT做匹配
題中兩個字符串匹配的必要條件是
如果我們將模式串反轉,那麼就是卷積的形式了,可以利用NTT來加速
那麼我們該如何計算這個式子呢?
可以手動把這個式子拆開,按照S_i的冪次下降來排列(一共只有5項)
由於乘法分配律,T項與P項是隻與j相關的,把它們加到一起,與S做卷積即可
做五次NTT乘法就行了
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 555555
const int mod=998244353;
int s[N],t[N],p[N],to[27],val[27];
char ch[N];
int wl,wn[N],rev[N];
int A[N],B[N],C[N];
inline int ksm(int x,int y)
{
if(x==1||x==0)return x;
if(y==2)return 1ll*x*x%mod;
if(y==3)return 1ll*x*x%mod*x%mod;
if(y==4)return 1ll*x*x%mod*x%mod*x%mod;
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
y>>=1;x=1ll*x*x%mod;
}
return ret;
}
void NTT(int a[],int len,int flg)
{
for(int i=1;i<len;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
for(int i=1,x=(wl>>1);i<len;i<<=1,x>>=1)for(int j=0;j<len;j+=(i<<1))
for(int k=j,y=0;k<i+j;k++,y+=x){
int tmp=1ll*a[k+i]*wn[flg==1?y:wl-y]%mod;
a[k+i]=(a[k]+mod-tmp)%mod;a[k]=(a[k]+tmp)%mod;
}
if(flg==-1)for(int i=0,ni=ksm(len,mod-2);i<len;i++)a[i]=1ll*a[i]*ni%mod;
}
void mul(int a[],int b[],int len)
{
NTT(a,len,1);NTT(b,len,1);
for(int i=0;i<len;i++)C[i]=(1ll*C[i]+1ll*a[i]*b[i])%mod;
}
int main()
{
srand(3993991);
int n,m,i,len=1;
for(i=1;i<=26;i++){
scanf("%d",&to[i]);
val[i]=1ll*rand()*rand()%1234567;
}
scanf("%s",ch);n=strlen(ch);
for(i=0;i<n;i++)t[i]=ch[n-i-1]-'a'+1;
for(i=0;i<n;i++)p[i]=val[to[t[i]]],t[i]=val[t[i]];
scanf("%s",ch+1);m=strlen(ch+1);
for(i=1;i<=m;i++)s[i]=val[ch[i]-'a'+1];
wl=1<<19;wn[0]=1;wn[1]=ksm(3,(mod-1)/wl);
for(i=2;i<=wl;i++)wn[i]=1ll*wn[i-1]*wn[1]%mod;
while(len<n+m)len<<=1;
for(i=1;i<len;i++)rev[i]=(rev[i>>1]>>1)|((i&1)?(len>>1):0);
for(i=0;i<len;i++)A[i]=ksm(s[i],4),B[i]=bool(i<n);
mul(A,B,len);
for(i=0;i<len;i++)A[i]=2ll*(mod-ksm(s[i],3))%mod,B[i]=(t[i]+p[i])%mod;
mul(A,B,len);
for(i=0;i<len;i++)A[i]=ksm(s[i],2),B[i]=(1ll*ksm(t[i],2)+1ll*ksm(p[i],2)+4ll*t[i]%mod*p[i]%mod)%mod;
mul(A,B,len);
for(i=0;i<len;i++)A[i]=2ll*(mod-s[i])%mod,B[i]=1ll*t[i]*p[i]%mod*(t[i]+p[i])%mod;
mul(A,B,len);
for(i=0;i<len;i++)A[i]=bool(i>=1&&i<=m),B[i]=1ll*ksm(t[i],2)*ksm(p[i],2)%mod;
mul(A,B,len);
NTT(C,len,-1);
for(i=n;i<=m;i++)if(!C[i])printf("1");else printf("0");
}
Harry The Potter
題解:
結論題
如果把所有的2操作看成一條邊,那麼在最優的答案中,2操作一定不會成環,否則換成1操作一定不會更劣
而2操作不成環就必定會連成許多棵樹
考慮一棵n個點樹怎樣才合法
如果把奇數深度的點與偶數深度的點分開來考慮
那麼一個操作就會使奇深度點權和與偶深度點權和之差+1或-1
我們的目的是要讓最後的奇偶深度點權和相等
對於一個集合,我們可以枚舉它的子集作爲偶數深度的點集,補集爲奇數深度的點
如果他們的和之差是<=|S|-1(因爲有|S|-1條邊可以調劑差值)並且與|S|-1同奇偶
那麼這個集合就可以構成一棵樹,減少一次1操作
我們設f[s]表示當前點集爲s,最多可以構成f[s]
則當s爲可行集合時,有f[s|t]=max(f[s|t],f[t]+1)可以更新答案
由於我們選的集合越多越好,我們一定不會用一個已經有答案的集合,再把它構成一棵樹去更新答案,這樣是無法讓答案變得更優的
所以我們可以只在f[s]=0是去判斷s是否可用,並且更新答案,這樣會快很多
代碼:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 1048578
#define LL long long
int con[N],lg[N],f[N];
LL a[N],sum[N];
bool check(int s)
{
for(int t=(s-1)&s;(t<<1)>=s&&t;t=(t-1)&s){
LL tmp=abs(sum[t]-sum[s^t]);
if(tmp<con[s]&&((con[s]-tmp)&1))return 1;
}
return 0;
}
int main()
{
int n,i,s,t,all;
scanf("%d",&n);
for(i=0;i<n;i++){
scanf("%lld",&a[i]);
if(a[i]==0)i--,n--;
}
all=(1<<n)-1;lg[0]=-1;
for(i=1;i<=all;i++){con[i]=con[i>>1]+(i&1);lg[i]=lg[i>>1]+1;}
for(i=1;i<=all;i++)sum[i]=sum[i-(i&-i)]+a[lg[i&-i]];
for(s=1;s<=all;s++)if(!f[s]&&check(s)){
int tmp=all^s;f[s]=1;
for(t=tmp;t;t=(t-1)&tmp)
f[s|t]=max(f[s|t],f[t]+1);
}
printf("%d",n-f[all]);
}
其實我們判斷一個集合是否可行是可以折半搜索的,複雜度是O(2^(n/2))
總複雜度通過二項式定理算出來是(1+sqrt(2))^n的,加上f[s]=0的剪枝可以跑到CF第一
Xor on Figures
題解:
二維循環卷積求逆
把矩陣第i行第j列看成,我們就可以定義這種矩陣的卷積,只不過這個卷積是帶有兩個未知數的
我們把操作數列對應的矩陣看作B,原矩陣爲A
那麼我們的問題就是求一個項數最少的矩陣C,使得C*B=A
我們發現這裏項與項之間的乘法其實是異或,B^2只會包含 B中所有項的平方
又因爲這是循環卷積,一個指數乘以2^k一定會被2^k整除,也就是一個矩陣B的2^k次方的所有項的xy指數都爲0,係數爲1
所以B^{2^k-1}就是B的逆元
那麼C(其實只有唯一解)就等於A*B^{2^k-1}
暴力乘出來就可以了
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 555
#define LL long long
LL a[N][N],b[N][N];int c[N][2];
int main()
{
int n,m,i,j,t,k,all;
scanf("%d",&n);all=(1<<n)-1;
for(i=0;i<=all;i++)for(j=0;j<=all;j++)scanf("%lld",&a[i][j]);
scanf("%d",&m);
for(i=1;i<=m;i++)scanf("%d%d",&c[i][0],&c[i][1]),c[i][0]--,c[i][1]--;
for(t=0;t<n;t++){
for(i=0;i<=all;i++)for(j=0;j<=all;j++)if(a[i][j])
for(k=1;k<=m;k++)
b[(i+c[k][0])&all][(j+c[k][1])&all]^=a[i][j];
for(i=0;i<=all;i++)for(j=0;j<=all;j++)a[i][j]=b[i][j],b[i][j]=0;
for(k=1;k<=m;k++)c[k][0]=(c[k][0]<<1)&all,c[k][1]=(c[k][1]<<1)&all;
}
int ans=0;
for(i=0;i<=all;i++)
for(j=0;j<=all;j++)
if(a[i][j])ans++;
printf("%d",ans);
}
Product Tuples:分治NTT版題
Red-White Fence:NTT版題
(略過)
藍超巨星、20200526c、畫家小P佔坑代填