2024.10.30 2022CCPC女生赛 D. Devil May Cry

题意

求长度为 n,元素为 1m 之间的整数,且满足下面两个条件的序列的数量:

  • i 的出现次数不超过 li
  • apisi,i=1,,r

n20,m100,r1000

前置知识 1. 子集卷积

首先本题有一个简单的状压 dp 做法:枚举 i 出现的位置集合,然后

f(i,S)=TS[TTi=][|T|li]f(i1,ST)

这个做法复杂度是 O(m3n),显然是过不了的。
我们令 Fi(x)=S[STi=][|S|li]xS,则答案为[xU]i=1mFi(x)U 表示全集。

注意这里的卷积是子集卷积而不是子集或卷积。二者的区别

[xS](FG)=TSFTGST[xS](FORG)=T1T2=SFT1GT2

即子集卷积要求没有重复元素。

子集卷积的一般做法是,将 Fi(x) 的系数改为 n 次多项式,xSyk 的系数是集合为 S,集合元素数量(重复元素算多次)为 k 的方案数。则 Fi(x,y)=S[STi=][|S|li]xSy|S|。这样最后 xUyn 的系数就是最终答案了。

加一维的好处是,我们已经把无法优化的子集卷积转化为可以用 FWT 优化的子集或卷积了。只不过子集或卷积的系数都是多项式。单次子集或卷积的加减次数为 O(n2n),而多项式的加减复杂度是 O(n),所以子集卷积的复杂度为 O(n22n)

总复杂度从 O(m3n) 优化到了 O(mn22n),还是无法通过 n=20 的数据。

PS:子集卷积的另一道板题是 [WC2018]州区划分

前置知识 2. 集合幂级数的对数和指数运算

系数是整数的集合幂级数是无法定义对数和指数运算的(log 和 exp 在模剩余系下没有定义)。但本题集合幂级数的系数都是多项式,因此可借助多项式的对数和指数运算定义和计算。

定义一个集合幂级数 F 的对数级数为(要求常数项为 1):

logF=k=1(1)k1(F1)kk

这个级数有意义是因为k>n(F1)k就是0了。

同理可以定义指数级数为(要求常数项为 0):

expF=k=0Fkk!

同样的,k>nFk 就是0了,所以级数是有意义的。

集合幂级数的对数和指数怎么算呢?很简单,FWT一遍,然后对 2n 个多项式分别求对数或指数,再IFWT一遍,就结束了。

现在回到原题。我们需要计算 i=1mFi(x),那么因为 log 和 exp 可以把连乘和连加互相转化,所以即计算 exp(i=1mlogFi(x))。那么我们只需快速计算所有 logFi(x) 即可。

——我会多项式全家桶!只需O(2nnlogn) 就能计算 logFi 了。

——醒醒,n20。多项式全家桶跑不过暴力递推。

关于 log 和 exp 的暴力递推求法(当然不是硬上定义式求),可以记下这个公式:

G=logF:g0=0,gn=fni=0n1igifni,G=expF:g0=1,gn=i=1nifigni

那么直接暴力还是 O(mn22n) 的(要做 m2n 次多项式 log)。如何进一步优化呢?

本题题解

我们把上面的算法流程写下来,看看能发现什么。

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;
}

看瓶颈部分。我们需要对所有的 i,S 计算 log([xS]FWT(Fi))

[xS]FWT(Fi)=TS[xT]Fi=TS[TTi=][|T|li]y|T|=t=0liytTSTi[|T|=t]=t=0li(|STi|t)yt

因此,本质不同的 [xS]FWT(Fi) 只有 O(n2) 种。我们可以预处理出这些多项式的 log。这样需要用的时候直接取出来即可。

另外,因为 FWT 是线性的,所以 log 的 IFWT 和 exp 的 FWT 可以抵消掉。这样我们就优化掉了对 Fi 的所有变换。

总复杂度 O(n4+mn2n+n22n)

#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那味了(

posted @   EssnSlaryt  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示