20200529小結(下)

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列全不爲空的方案就是

\sum_{i=0}^{n-k-1}(-1)^iC(n-k,i)*(n-k-i)^n

代碼:

#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做匹配

題中兩個字符串匹配的必要條件是

\sum_{i=1}^{|S|}(S_i-T_i)^2(S_i-P_{T_i})^2

如果我們將模式串反轉,那麼就是卷積的形式了,可以利用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列看成a*x^iy^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佔坑代填

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章