原题链接:https://www.luogu.com.cn/problem/P3214
题意解读:从数字1~n组成的所有非空子集中,选m个子集组成集合T,要求集合T里所有子集的数字都出现偶数次,请所有T的方案数。
解题思路:
数字1~n组成的非空子集一共有2n-1个(每个数字有选或不选两种,排除全不选)
如果不考虑数字出现偶数次的限制,总的方案数为C(2n-1,m),关键要思考如何排除掉出现偶数次的情况。
这里借助于递推:
设f[i]表示选了前i个子集且所有数都出现偶数次的方案数
考虑选第i个子集的时候,方案是确定的,因为前i-1如果已经选好,所有数字的奇偶性是确定的,如果不考虑奇偶性,选前i-1个子集的所有方案数是C(2n-1,i-1)
选第i个子集有两种不合法的可能:
1、选空集合,前i-1个子集中数字都已经出现偶数次
由于C(2n-1,i-1)里包括了选空集的情况,前i-1个集合数字是偶数个的时候,第i个为空集,一共有f[i-1]种
2、第i个子集与前i-1个子集有重复的情况
首先,有i-2个子集是不重复的,还有2个子集重复,重复的子集有2n-1-(i-2)=2n-i+1种选择,i-2个子集是合法的方案数是f[i-2],因此第i个子集与前i-1个子集有重复的情况一共有f[i-2]*(2n-i+1)种
这时,得到选前i个子集的总方案数是C(2n-1,i-1)-f[i-1]-f[i-2]*(2n-i+1)
去掉不合法的情况之后,对于i个子集都确定的情况下,第i个子集一共有i种选法,方案数里已经包括了这i种选法,因此最终:
f[i] = ( C(2n-1,i-1)-f[i-1]-f[i-2]*(2n-i+1) ) / i
C(2n-1,i-1)可以通过展开后阶乘逆元在递推中来计算,C(2n-1,i-1) = (2n-1)*...*(2n-i+1) / (i-1)!
初始时f[0]=f[1]=f[2]=0,从i=3开始求,C(2n-1,3-1) = (2n-1)*...*(2n-2) / 2!,
下一次递推C(2n-1,4-1) = C(2n-1,3-1) * (2n-3) / 3,因此下一次C(2n-1,i-1) 要*(2n-i) / i
除以i变为乘以i模mod的逆元。
100分代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;const int N = 1000005, MOD = 1e8 + 7;
LL f[N], C;
LL n, m;LL ksm(LL a, LL b, LL mod)
{LL res = 1;while(b){if(b & 1) res = res * a % mod;a = a * a % mod;b >>= 1;}return res;
}int main()
{cin >> n >> m;//C(2^n-1,3-1) = (2^n-1)*...*(2^n-2) / 2!LL t = ksm(2, n, MOD);C = (t - 1) * (t - 2) % MOD * ksm(2, MOD - 2, MOD) % MOD; for(int i = 3; i <= m; i++){//f[i] = ( C(2^n-1,i-1)-f[i-1]-f[i-2]*(2^n-i+1) ) / if[i] = C - f[i - 1] - f[i - 2] * (t - i + 1) % MOD;f[i] = (f[i] % MOD + MOD) % MOD * ksm(i, MOD - 2, MOD) % MOD;//C = C * (2^n-i+1) / iC = C * (t - i) % MOD * ksm(i, MOD - 2, MOD) % MOD;}cout << f[m];return 0;
}