题解 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}\)__

posted @ 2020-09-09 16:26  Ame_sora  阅读(108)  评论(0编辑  收藏  举报