[BZOJ2339][HNOI2011]卡农(DP+组合数学)

这题真的变态。HN太厉害了!
发现题目中定义的同种音乐其实是假的,只要除以 m! m ! 就可以了(利用逆元)。
这样就是在集合 S={1,2,...,n} S = { 1 , 2 , . . . , n } 中选出 m m 个子集,满足三点性质:
(1)所有选出的m个子集都不能为空。
(2)所有选出的 m m 个子集中,不能存在两个完全一样的集合。
(3)所有选出的m个子集中, 1 1 n每个元素出现的次数必须是偶数。
DP。定义状态 f[i] f [ i ] 表示到第 i i 个子集,满足所有三点性质的方案数。
考虑从容斥的角度分析,得出转移。由性质(3)得到,确定了前i1个子集后,第 i i 个子集也随之确定。这时方案数为A2n1i1
然后去掉不满足性质(1)的方案数。可以得出,如果第 i i 个子集为空,那么前i1个子集可以凑成一个合法的方案。因此不满足性质(1)的方案数为 f[i1] f [ i − 1 ]
最后去掉不满足性质(2)的方案数。假设第 j j 个子集与第i个子集重复,那么如果把第 j j 个子集和第i个子集去掉,剩下的 i2 i − 2 个子集可以凑成一个合法的方案,即 f[i2] f [ i − 2 ] 。而 j j i1种取值,子集 i i 的取法为2n1(i2)
因此不满足性质(2)的方案数为 f[i2]×(i1)×(2ni+1) f [ i − 2 ] × ( i − 1 ) × ( 2 n − i + 1 )
得出转移:

f[i]=Ai12n1f[i1]f[i2]×(i1)×(2ni+1) f [ i ] = A 2 n − 1 i − 1 − f [ i − 1 ] − f [ i − 2 ] × ( i − 1 ) × ( 2 n − i + 1 )

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1e6 + 5, MX = 1e8 + 7; int n, m, f[N], orz = 1, mx = 1, A[N];
int qpow(int a, int b) {
    int res = 1; while (b) b & 1 ? res = 1ll * res * a % MX : 0,
        a = 1ll * a * a % MX, b >>= 1; return res;
}
int main() {
    int i; cin >> n >> m; for (i = 1; i <= n; i++) orz = orz * 2 % MX;
    orz = (orz - 1 + MX) % MX; A[0] = 1; for (i = 1; i <= m; i++)
        A[i] = 1ll * A[i - 1] * ((orz - i + 1 + MX) % MX) % MX,
    mx = 1ll * mx * i % MX; f[f[1] = 0] = 1; for (i = 2; i <= m; i++)
        f[i] = (A[i - 1] - f[i - 1] + MX - 1ll * f[i - 2] *
        (i - 1) % MX * (orz - i + 2 + MX) % MX + MX) % MX;
    cout << 1ll * f[m] * qpow(mx, MX - 2) % MX << endl; return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值