题解 P3214 【[HNOI2011]卡农】
简化题面:
从集合{\(1,2,3....n\)}中选择\(m\)个互不相同的非空集合且所有元素出现次数为偶数的方案数。
解题思路:
因为对于集合位置不同的方案数算1,因此我们考虑像全排列变组合数一样对无序的方案数除以\(m!\)就是我们所要的答案。
第一眼看上去像是第二类斯特林数?但是仔细一看并不是。考虑是\(dp[i]\)为选\(i\)个集合并且满足限制条件的有序方案数。
直接求很困难因此考虑容斥,总方案减不合法方案就是最后的答案
考虑总方案是什么,由于限制元素所有元素出现次数必须为偶数,因此对于\(i\)是确定的,因此总方案为\(2^n-1\)的集合(注意这里舍去了空集)中选取\(i-1\)的排列\(A_{2^n-1}^{i-1}\)
对于不合法的方案我们考虑两种情况
1,前\(i-1\)个集合合法但第\(i\)个为空集,因此还是\(dp[i-1]\)。
2,前\(i-1\)个集合合法第\(i\)个集合和前面相同,因此我们要将两个重复的集合都删去,即为\(dp[i-2]\times (i-1)\times (2^n-1-(i-2))\)
那么对于\(dp[i]\)我们就有了递推式\(dp[i]=A_{2^n-1}^{i-1}-dp[i-1]-dp[i-2]\times (i-1)\times (2^n-1-(i-2))\)
最后答案除以\(m!\)即可
Code
#include<bits/stdc++.h>
#define LL long long
#define _ 0
using namespace std;
/*Grievous Lady*/
template <typename _n_> void mian(_n_ & _x_){
_x_ = 0;int _m_ = 1;char buf_ = getchar();
while(buf_ < '0' || buf_ > '9'){if(buf_ == '-')_m_ =- 1;buf_ = getchar();}
do{_x_ = _x_ * 10 + buf_ - '0';buf_ = getchar();}while(buf_ >= '0' && buf_ <= '9');_x_ *= _m_;
}
const int kato = 1e6 + 10;
#define mod 100000007
inline LL quick_pow(LL a , LL b){
LL res = 1;
for(; b ; b >>= 1 , a = a * a % mod){
if(b & 1){
res = res * a % mod;
}
}
return res % mod;
}
LL n , m , fac = 1;
LL A[kato] , dp[kato];
inline int Ame_(){
mian(n) , mian(m);
int poi = quick_pow(2 , n) - 1;
A[0] = 1;
dp[0] = 1 , dp[1] = 0;
for(int i = 1;i <= m;i ++){
A[i] = (A[i - 1] * ((poi - i + 1 + mod) % mod)) % mod;
}
for(int i = 2;i <= m;i ++){
dp[i] = (A[i - 1] - dp[i - 1] - dp[i - 2] * (i - 1) % mod * (poi - i + 2 + mod) % mod + mod) % mod;
}
for(int i = 2;i <= m;i ++){
fac = fac * i % mod;
}
printf("%lld\n" , dp[m] * quick_pow(fac , mod - 2) % mod);
return ~~(0^_^0);
}
int Ame__ = Ame_();
int main(){;}
-----\(\begin{aligned}\mathcal{Ame}\end{aligned}\)__
呐,这份感情,什么时候可以传达呢