P3214 [HNOI2011] 卡农 (dp +排列计数 正难则反)

 

题目传送门

题目大意:

给定两个数 \(n , m\) ,在集合 \(S = {1,2...n}\) 中选取 \(m\) 个非空子集,使得子集不重复并且子集中的每个元素出现偶数次,求出满足上述条件的方案数。
假设 \(a\)\(\{\{1,2\},\{2,3\}\}\)\(b\)\(\{\{2,3\},\{1,2\}\}\),那么 \(a\)\(b\) 为重复情况。(笔者定义这种情况为假相同)

题目分析:

  • [\(1\)]:正难则反。我们分析题目中的条件,首先对于偶数次的情况来说还算好处理,对于重复的情况,我们是否能够方便快速的处理这种情况呢,对于笔者来说太难了,在笔者的知识范围内计数问题不是计数 \(dp\) 就是组合数学,那么运用组合数学的知识需要去掉某个集合重复 \(2,3,4...n\)次的情况,所以我们不考虑,那么对于 \(dp\) 来说,我们可以设计出 \(dp_{i,j}\) 为前 \(i\) 中子集取了 \(j\) 个子集的方案数,而 \(i\) 又太大了所以说放弃,我们来考虑用 总方案数 \(-\) 不合法的方案数 (欢迎各位大佬指出错误)

  • [\(2\)]:首先我们考虑,\(S\) 的子集有多少种,根据二项式定理可知, \(S\) 的子集有 \(2^n - 1\) 种。那么我们不考虑假相同的情况,因为这个可以再最后直接除 \(m!\) 的阶乘来处理,在这种情况下总方案数为 \(A_{2^n-1}^m\)

  • [\(3\)]:我们在 \(1\) 的基础上来考虑出现偶数次如何处理。对于一个选取 \(i\) 个子集使子集中所有元素出现次数为偶数的情况,我们可以由选取 \(i - 1\) 个子集出现次数不固定的方案数来确定。因为对于选取 \(i - 1\) 个子集的时候,对于出现偶数次的元素在第 \(i\) 个子集中一定不会出现,对于奇数次的元素在第 \(i\) 个集合中一定会出现,那么第 \(i\) 个集合就固定下来了,所以我们只需求出选取 \(i - 1\) 个子集的总方案数即可,为 \(A_{2^n - 1}^{i-1}\)

  • [\(4\)]:我们定义 \(f_i\) 为选取 \(i\) 个子集的方案数。接下来我们来考虑非空的情况,若我们在第 \(i\) 个子集选取了一个空集,那么这个空集对方案数不产生影响,故我们只需去掉 \(f_{i - 1}\) 即可。

  • [\(5\)]:最后我们来处理子集重复的情况。若我们选的第 \(i\) 个子集与前面某个子集 \(j\) 重复,那么 \(i\) 的取值有 \(i - 1\)种,而去掉子集 \(i , j\) 所能构成的合法方案数为 \(f_{i,j}\),最后思考 \(j\) 有多少种取值,我们一共有 \(2^n - 1\) 个子集,而前面用掉了 \(i - 2\) 个,故 \(j\) 的选取有 \(2^n-1-(i - 2)\) 种。那么我可可得递推式 \(f_i = A_{2^n-1}^{i-1} - f_{i - 1} - (i - 1) *(2^n-1-(i - 2)) * f_{i - 2}\)

代码实现:

我们只需要处理出 \(2^n-1\) , \(m!\), \(A_{2^n-1}^i\)即可,最后处理一下逆元,用 \(f_m * \frac{1}{m!}\) 即可

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e8 + 7;
const int M = 1e6 + 7; 
int f[M] , A[M];
int Pow(int a, int b) {
	int ans = 1;
	while(b) {
		if(b & 1) ans = ans * a % mod;
		a = a * a % mod;
		b >>= 1;	
	}
	return ans;
}	
signed main () {
	int n , m; cin >> n >> m;
	int _2 = 1;
	for(int i = 1; i <= n; ++ i) _2 = _2 * 2 % mod;
	_2 = (_2 + mod - 1) % mod;
	A[0] = 1;
	int jc = 1;
	for(int i = 1; i <= m; ++ i) A[i] = A[i - 1] * ((_2 - i + 1 + mod) % mod) % mod , jc = jc * i % mod;
	f[f[1] = 0] = 1;
	for(int i = 2; i <= m; ++ i) f[i] = (A[i - 1] - f[i - 1] + mod - f[i - 2] * (i - 1) % mod * (_2 - i + 2 + mod) % mod + mod) % mod;
	cout << f[m] * Pow(jc , mod - 2) % mod;
}

[========]

完结!

posted @ 2022-09-05 23:50  L3067545513  阅读(31)  评论(0编辑  收藏  举报