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占坑代填

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