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

 

题目传送门

题目大意:

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

题目分析:

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

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

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

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

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

代码实现:

我们只需要处理出 2n1 , m!, A2n1i即可,最后处理一下逆元,用 fm1m! 即可

点击查看代码
#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 @   L3067545513  阅读(40)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示