2024.10.30 2022CCPC女生赛 D. Devil May Cry
题意
求长度为
的出现次数不超过
前置知识 1. 子集卷积
首先本题有一个简单的状压 dp 做法:枚举 i 出现的位置集合,然后
这个做法复杂度是
我们令
注意这里的卷积是子集卷积而不是子集或卷积。二者的区别
即子集卷积要求没有重复元素。
子集卷积的一般做法是,将
加一维的好处是,我们已经把无法优化的子集卷积转化为可以用 FWT 优化的子集或卷积了。只不过子集或卷积的系数都是多项式。单次子集或卷积的加减次数为
总复杂度从
PS:子集卷积的另一道板题是 [WC2018]州区划分
前置知识 2. 集合幂级数的对数和指数运算
系数是整数的集合幂级数是无法定义对数和指数运算的(log 和 exp 在模剩余系下没有定义)。但本题集合幂级数的系数都是多项式,因此可借助多项式的对数和指数运算定义和计算。
定义一个集合幂级数
这个级数有意义是因为
同理可以定义指数级数为(要求常数项为 0):
同样的,
集合幂级数的对数和指数怎么算呢?很简单,FWT一遍,然后对
现在回到原题。我们需要计算
——我会多项式全家桶!只需
——醒醒,
关于 log 和 exp 的暴力递推求法(当然不是硬上定义式求),可以记下这个公式:
那么直接暴力还是
本题题解
我们把上面的算法流程写下来,看看能发现什么。
Poly F[M][S],ans[S]; int main(){ // 算 F for(int i=1;i<=m;++i){ FWT(F[i]); for(int j=0;j<1<<n;++j) Log(F[i][j]); IFWT(F[i]); for(int j=0;j<1<<n;++j) ans[j]+=F[i][j]; } FWT(ans); for(int j=0;j<1<<n;++j)Exp(ans[j]); IFWT(ans); cout<<ans[(1<<n)-1][n]<<endl; }
看瓶颈部分。我们需要对所有的
因此,本质不同的
另外,因为 FWT 是线性的,所以 log 的 IFWT 和 exp 的 FWT 可以抵消掉。这样我们就优化掉了对
总复杂度
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=21,M=105,S=1<<20,mod=998244353; int sub(int x,int y){return x^(x&y);} ll inv[N],c[N][N]; valarray<ll> Log(const valarray<ll>& f){ int n=f.size()-1; valarray<ll> g(n+1); for(int i=1;i<=n;++i){ ll tmp=0; for(int j=1;j<i;++j) tmp=(tmp+j*g[j]%mod*f[i-j])%mod; g[i]=(f[i]-inv[i]*tmp%mod+mod)%mod; } return g; } valarray<ll> Exp(const valarray<ll>& f){ int n=f.size()-1; valarray<ll> g(n+1); g[0]=1; for(int i=1;i<=n;++i){ for(int j=1;j<=i;++j) g[i]=(g[i]+j*f[j]%mod*g[i-j])%mod; g[i]=g[i]*inv[i]%mod; } return g; } void FWT_OR(valarray<ll>* a,int n){ for(int k=1;k<n;k<<=1) for(int i=0;i<n;i+=k<<1) for(int j=0;j<k;++j) (a[i+j+k]+=a[i+j])%=mod; } void IFWT_OR(valarray<ll>* a,int n){ for(int k=1;k<n;k<<=1) for(int i=0;i<n;i+=k<<1) for(int j=0;j<k;++j) (a[i+j+k]+=mod-a[i+j])%=mod; } valarray<ll> g[N][N]; void init(int n){ inv[1]=1; for(int i=2;i<=n;++i)inv[i]=inv[mod%i]*(mod-mod/i)%mod; for(int i=0;i<=n;++i){ c[i][0]=1; for(int j=1;j<=i;++j) c[i][j]=c[i-1][j]+c[i-1][j-1]; } for(int i=0;i<=n;++i) for(int j=0;j<=n;++j){ g[i][j].resize(n+1); for(int t=0;t<=j;++t)g[i][j][t]=c[i][t]; g[i][j]=Log(g[i][j]); } } int n,m,lim[M],q,t[M],x,y; valarray<ll> f[S]; int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n>>m; init(n); for(int i=1;i<=m;++i)cin>>lim[i]; cin>>q; while(q--)cin>>x>>y,--x,t[y]|=1<<x; for(int i=0;i<1<<n;++i){ f[i].resize(n+1); for(int j=1;j<=m;++j) f[i]=(f[i]+g[__builtin_popcount(sub(i,t[j]))][lim[j]])%mod; f[i]=Exp(f[i]); } IFWT_OR(f,1<<n); cout<<f[(1<<n)-1][n]<<'\n'; }
valarray是个好东西。用起来有点py那味了(
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具