洛谷 P6570 - [NOI Online #3 提高组] 优秀子序列(集合幂级数+多项式)
首先 \(3^n\) 的做法就不多说了,相信对于会状压 dp+会枚举子集的同学来说不算困难(暴论),因此这篇博客将着重讲解 \(2^nn^2\) 的做法。
首先如果我们把每个 \(a_i\) 看作一个集合幂级数 \(1+x^{a_i}\),那么我们的任务就是把所有这样的集合幂级数做一遍子集卷积对吧。直接做一脸过不去。不过注意到这个式子的形式比较特别,事实上学过多项式&生成函数的同学应该对形如 \(1+x^k\) 的式子特别敏感,因为在生成函数那套理论中有个恒等式 \(\ln(1+x^k)=\sum\limits_{i}(-1)^{i+1}\dfrac{x^{ik}}{i}\),因此考虑将这东西与多项式扯上关系。考虑子集卷积的本质:将所有 \(x^{S}\) 看作一个二维函数 \(x^{S}y^{|S|}\),然后对 \(x\) 的指数做 or 卷积,对 \(y\) 的指数做加法卷积。那么我们考虑做这样一件事:把所有集合幂级数 \(F(S)\) 写成 \(\sum\limits_{S}F_{S}(y)x^S\) 的形式,也就是外层是集合幂级数,内层是一个关于 \(y\) 的多项式。那么考虑两个幂级数 \(F(S)\) 和 \(G(S)\) 做子集卷积得到的幂级数 \(H(S)\),必然有 \(H_i(y)=\sum\limits_{j|k=i}F_j(y)G_k(y)\),因此在这种定义下,对两个幂级数进行子集卷积的过程即是:将 \(F_{S}(y)x^S\) 做一遍 FWTor,也就是把平时对整数的 FWTor 的加法改为多项式加法,对 \(G_S(y)x^S\) 也做一遍同样的操作,然后令 \(H_S(y)=F_S(y)G_S(y)\),也就是将 FWT 后对应位置上的多项式卷起来,然后再 IFWTor 回去即可。
直接照着上面的方式做还是会 TLE,不过注意到将集合幂级数 \(1+x^{a_i}\) 进行一遍 FWTor 后得到的集合幂级数比较特别,具体来说,\(1=x^0y^0\) 显然可以对 FWTor 后所有位置上的数产生贡献,因此所有位置上的 \(F_S(y)\) 都有一个 \(y^0\),而 \(x^{a_i}=x^{a_i}y^{|a_i|}\) 显然只能对 \(a_i\in S\) 的 \(S\) 产生贡献,因此对于所有 \(a_i\in S\) 的 \(S\) 有 \(F_S(y)=y^{|a_i|}+1\),其余 \(S\) 有 \(F_{S}(y)=1\)。
它 出 现 了!\(y^{a_i}+1\) 显然与前面 \(1+x^k\) 是同一形式的,因此它的 \(\ln\) 我们也是可以非常轻松求得的,而我们在 FWTor 之后,按照套路是要把对应位置上的多项式全部卷起来的,按照多项式的套路我们可以先取 \(\ln\) 再 \(\exp\) 回去,看,你要的 \(\ln(y^{a_i}+1)\) 不就来了吗?我们记 \(H(S)\) 为将所有集合幂级数卷起来后得到的集合幂级数,那么对于所有 \(S\),\(\ln(\text{FWT}(H_S(y)))\) 是很好求的,具体步骤是:我们先记 \(F_{i,S}(y)\) 为一个幂级数,满足对于 \(S=a_i\),\(F_{i,S}(y)=\ln(y^{|a_i|}+1)\),其余 \(F_{i,S}=0\),对这东西对应位置上的多项式求个和,然后跑遍高维前缀和(或者你爱叫它 FWTor 我也没意见)即可,求完 \(\ln(H_S(y))\) 以后 \(\exp\) 回去即可得到 \(\text{FWT}(H_S(y))\),然后再一波 IFWTor 即可得到真正的系数,由于多项式的长度最多只有 \(18\),因此 \(\exp\) 不用任意模数 NTT,直接暴力求即可。
const int MAXN=1<<18;
const int LOG_V=18;
const int MOD=1e9+7;
int pr[MAXN/6+5],prcnt=0,vis[MAXN+5],phi[MAXN+5];
void sieve(int n){
phi[1]=1;
for(int i=2;i<=n;i++){
if(!vis[i]) pr[++prcnt]=i,phi[i]=i-1;
for(int j=1;j<=prcnt&&pr[j]*i<=n;j++){
vis[pr[j]*i]=1;
if(i%pr[j]==0){phi[i*pr[j]]=phi[i]*pr[j];break;}
phi[i*pr[j]]=phi[i]*phi[pr[j]];
}
}
}
int n,a[MAXN+5],f[MAXN+5][LOG_V+2],tmp[LOG_V+2],inv[LOG_V+2];
int main(){
scanf("%d",&n);sieve(MAXN);
for(int i=(inv[0]=inv[1]=1)+1;i<=LOG_V;i++) inv[i]=1ll*inv[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1,x;i<=n;i++) scanf("%d",&x),a[x]++;
for(int i=1;i<MAXN;i++){
int cnt=__builtin_popcount(i);
for(int j=1;j<=LOG_V/cnt;j++){
if(j&1) f[i][j*cnt]=(f[i][j*cnt]+1ll*inv[j]*a[i])%MOD;
else f[i][j*cnt]=(f[i][j*cnt]-1ll*inv[j]*a[i]%MOD+MOD)%MOD;
}
}
for(int i=2;i<=MAXN;i<<=1)
for(int j=0;j<MAXN;j+=i)
for(int k=0;k<(i>>1);k++)
for(int l=0;l<=LOG_V;l++){
f[(i>>1)+j+k][l]=(f[(i>>1)+j+k][l]+f[j+k][l])%MOD;
}
for(int i=0;i<MAXN;i++){
memset(tmp,0,sizeof(tmp));tmp[0]=1;
for(int j=1;j<=LOG_V;j++){
for(int k=0;k<j;k++)
tmp[j]=(tmp[j]+1ll*f[i][j-k]*(j-k)%MOD*tmp[k])%MOD;
tmp[j]=1ll*tmp[j]*inv[j]%MOD;
}
for(int j=0;j<=LOG_V;j++) f[i][j]=tmp[j];
}
for(int i=2;i<=MAXN;i<<=1)
for(int j=0;j<MAXN;j+=i)
for(int k=0;k<(i>>1);k++)
for(int l=0;l<=LOG_V;l++){
f[(i>>1)+j+k][l]=(f[(i>>1)+j+k][l]-f[j+k][l]+MOD)%MOD;
}
int ans=0;
for(int i=0;i<MAXN;i++) ans=(ans+1ll*phi[i+1]*f[i][__builtin_popcount(i)])%MOD;
for(int i=1;i<=a[0];i++) ans=2*ans%MOD;
printf("%d\n",ans);
return 0;
}