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