Loading

“罗生门”题解

罗生门

题目描述

选择 \(n\)\([0,2^n)\) 的数,其中数 \(i\) 只能选 \(a_i\) 次,求 \(\forall i\in [1,n]\)\(i\) 个数的二进制下第 \(i\) 低位都为 \(1\) 的方案数,对 \(998244353\) 取模。

\(n\le 20,0\le a_i<998244353\)

题解

首先有一个暴力的 dp,令 \(dp_{i,S}\) 表示考虑数值为 \([0,i]\) 的数,被分配到了 \(S\) 中的位置。代码:

f[1<<n][0]=1;//倒着更新,好设边界 
for(int i=(1<<n)-1;i>=0;i--){
    for(int s=0;s<1<<n;s++){
        int tmp=i&s;
        for(int t=tmp,lst=1;lst;lst=t,t=(t-1)&tmp){//t直接就是覆盖那些 
            if(pc(t)>a[i])continue;
            (f[i][s]+=f[i+1][s^t]*ca[i][pc(t)])%=MOD;//ca[i][j]=A(a[i],j)
        }
    }
}
cout<<f[0][(1<<n)-1];

复杂度分析:

考虑这三重循环将全集划分为了五个部分,每个元素都有可能在其中一个状态中,所以复杂度为 \(\mathcal O(5^n)\)

原题相当于求选出 \(n\) 个数然后恰好分配到 \(n\) 个位置的方案数。从上述dp可以看出,为了满足“恰好”,我们不得不记录当前的状态。

于是可以考虑反演,令 \(f_S\) 表示选出 \(n\) 个数,可以重复的放置位置,但只能放在 \(S\) 集合中的位置的方案数,相当于“至多”,根据反演公式有:\(ans=\sum_{S}(-1)^{n-|S|}f_S\)

现在考虑怎么求 \(f_S\),由于各个数值填放位置之间的选择没有影响了,可以单独来看。对于数 \(i\),其可以放置的位置有 \(|i\cap S|\) 个。这个技巧在“P3349 [ZJOI2016] 小星星” 中用过。

为了将各个数值合并,并满足“选择 \(n\) 个数”的要求,我们用生成函数,数值 \(i\) 的生成函数位 \(1+|i\cap S|x\),总共有 \(a_i\) 个就是 \((1+|i\cap S|x)^{a_i}\),最终得到 \(\prod_{i=0}^{2^n-1}(1+|i\cap S|x)^{a_i}\)

可以将 \(|i\cap S|\) 相同的项合并,令 \(c_i=\sum_{|T\cap S|=i}a_T\),得到\(\prod_{i=1}^{n}(1+ix)^{c_i}\)

可以看出这种积式可以用 \(\ln+\exp\) 解决。考虑如何快速算出 \(c_i\)考虑预处理

\(dp_{i,s1,s2,j}=\sum_{x\in \{x|x[i\sim n]=s1\land |x[0,i-1]\cap s2|=j\}}a_x\),即第 \(i\sim n\) 为等于 \(s1\),第 \(0\sim i-1\) 位与 \(s2\) 的交位数为 \(j\)\(a\) 之和。那么枚举第 \(i\) 位的 \(s2\) 的值,与 此时 \(s1\) 相与后累加进 \(j\) 即可\(\mathcal O(1)\) 转移。

由于 \(s1,s2\) 分别表示了前后两部分,可以直接存进一维变为 \(dp_{i,s,j}\),复杂度 \(\mathcal O(2^nn^2)\)

然后根据 \(\prod_{i=1}^{n}(1+ix)^{c_i}=\exp(\sum_{i=1}^{n}c_i\ln(1+ix))\),泰勒展开 \(\ln(1+ax)=\sum_{i=1}(-1)^{i+1}\frac{a^i}{i}x^i\),以及 \(exp(F)'=exp(F)\times F'\) 的求导递推式可以在 单次 \(\mathcal O(n^2)\) 的时间内算出。

总复杂度 \(\mathcal O(2^nn^2)\)

关于卡常(以下缺一不可):

  • 实现时需要注意的是,由于 \(n\) 很小, \(n\approx \log_2 n\) ,因此 \(\mathcal O(2^nn\log n)\) 的部分也会对常数造成较大影响,应该优化掉

    (g[i+1]*=QP(i+1,MOD-2)%=MOD;//外面套了O(n2^n)的枚举

    改为

    (g[i+1]*=inv[i+1])%=MOD;//外面套了O(n2^n)的枚举

  • 减少不必要取模,

    (f[j]+=Sign(j+1)*v*inv[j]%MOD*c[i]%MOD)%=MOD;

    改为

    (f[j]+=Sign(j+1)*v*inv[j]%MOD*c[i])%=MOD;

posted @ 2025-04-17 20:51  lupengheyyds  阅读(18)  评论(0)    收藏  举报