【题解】AtCoder [ARC096C] Everything on it

bikuhiku

2022-10-19 20:30:49

Solution

看到这种计数题,觉得很难正推的,多半是反推,还多半沾点容斥。 *** 我们考虑 $dp_i$ 表示有已确定的 $i$ 种元素不合法,其它 $n-i$ 种元素不确定(任意)的方案数,那么可以容斥一下,答案即为: $$ans = \sum\limits_{i = 0}^{n}(-1)^n \times \binom{n}{i} \times dp_i$$ 其中 $\binom{n}{i}$ 就表示从 $n$ 碗面中选择了 $i$ 种。 *** 既然 $dp_i$ 中的元素被鲜明地分为了两类,设 $f_i$ 表示确定的 $i$ 种不合法元素的方案数,则有: $$dp_i = f_i \times 2^{2^{n-i}}$$ 其中 $2^{2^{n-i}}$ 意即:$n-i$ 种元素构成了 $2^{n-i}$ 个集合,而这些集合又作为元素构成了 $2^{2^{n-i}}$ 个集合。 注意一下求解 $2^{2^{n-i}}\bmod p$ 请用欧拉定理,变为求解 $2^{2^{n-i}\bmod \varphi(p)}\bmod p$。 *** 压力来到了求 $f_i$ 上。我们考虑这 $i$ 个元素分别放到了哪些集合里,设 $g_{i,j}$ 表示 $i$ 个元素放到了 $j$ 个集合里的方案数,则又有: $$f_i = \sum\limits_{j = 0}^{n}\left(2^{n-i}\right)^{j}\times g_{i,j}$$ 即先枚举这 $i$ 个元素放到哪些集合里。 *** 怎么求 $g_{i,j}$?可以考虑递推: $$g_{i,j} = g_{i-1,j-1}+(j+1)\times g_{i-1,j}$$ 1. 从 $g_{i-1,j-1}$ 转移:剩下的一种元素必须加到一个新的集合里; 2. 从 $g_{i-1,j}$ 转移:无论把这种元素加到那个集合里、甚至不加入任一个集合都可以。 似乎推组合数、斯特林数时也有类似的式子,也可以说是递推的一个技巧吧。 *** 那就开始码代码吧。 ```cpp #include <iostream> const int N = 3005; int n, mod; int C[N][N], g[N][N], ans; int quick_pow(int _a,int _n,int _p = mod) { int _res = 1; while(_n) { if(_n&1) _res = 1ll*_res*_a%_p; _a = 1ll*_a*_a%_p; _n >>= 1; } return _res; } int main() { scanf("%d %d",&n,&mod); for(int i = 0;i <= n;++i) { g[i][0] = C[i][0] = 1; for(int j = 1;j <= i;++j) { g[i][j] = (g[i-1][j-1]+(j+1LL)*g[i-1][j])%mod; C[i][j] = (C[i-1][j]+C[i-1][j-1])%mod; } } for(int i = 0;i <= n;++i) { int f = 0, pass = 1; int t_n = quick_pow(2,n-i); for(int j = 0;j <= i;++j) { f = (f+1ll*g[i][j]*pass)%mod; pass = 1ll*pass*t_n%mod; } ans = (ans+1ll*f*(i&1 ? mod-C[n][i] : C[n][i])%mod*quick_pow(2,quick_pow(2,n-i,mod-1)))%mod; } printf("%d\n",ans); return 0; } ```