bzoj2339: [HNOI2011]卡农

【题意】
  求出由多少个不同的集合满足下列条件:

    1.集合内含有m个不同的非空数集,每个数集由若干个不同的1~n的数组成。

    2.集合内每个1~n的整数在本集合的所有数集中总共出现偶数次。
  以上提到的集合和数集均不考虑元素的顺序,即含有的元素相同但元素顺序不同被视为同一种集合。
【题解】
  还是得强调看了题解,思想很巧妙。。。。
  直接套式子求出题意所求的集合数是比较困难的。
  所以考虑补集转化的思想。
  首先我们把所有元素顺序不同的集合当不同的算,这样会比较容易,只要方案数最后除去m!就是答案了。。。
  设f[x]为仅需选取x个不同数集就满足条件2的集合数。
  假设为了满足条件2,那么显然在选取了m-1个不同数集之后,剩下那个数集是确定的。
  这样就有P(2^n-1,m-1)这样的数集。
  但这个确定的数集被选取之后可能会不满足条件1。总的有2种情况:
    1.前m-1个集合已满足条件2,使得剩下的数集为空集。(有f[m-1]个这样的集合)
    2.选取的剩下那个数集与之前m-1个的某一个相同。(有f[m-2]*(m-1)*(2^n-1-(m-2))个……)
  所以我们得减去这2种集合的数量。
  递推式即为:
  f[m]=A(2^n-1,m-1)-f[m-1]-f[m-2]*(m-1)*(2^n-1-(m-2))
  最后只要除以m!就行了。
【代码】

 1 #include <iostream>
 2 #include <cstdio>
 3 typedef long long ll;
 4 using namespace std;
 5 const int mo=1e8+7;
 6 const int N=1000005;
 7 int a,n,m,s,f[N],A[N];
 8 int exp(int x,int k,int p)
 9 {
10     int s=1;
11     for (;k;x=(ll)x*x%p,k>>=1)
12         if (k&1)    s=(ll)s*x%p;
13     return s;
14 }
15 int inv(int x,int p)
16 {
17     return exp(x,p-2,p);
18 }
19 void pre()
20 {
21     a=exp(2,n,mo)-1;
22     A[0]=1;
23     for (int i=1;i<=m;++i)
24         A[i]=(ll)A[i-1]*((a-i+1)+mo)%mo;
25 }
26 int main()
27 {
28     cin>>n>>m;    
29     pre();
30     f[0]=1;f[1]=0;
31     for (int i=2;i<=m;++i)    
32         f[i]=((A[i-1]-f[i-1]-(ll)f[i-2]*(i-1)%mo*(a-(i-2)+mo)%mo)%mo+mo)%mo;
33     s=1;        
34     for (int i=1;i<=m;++i)
35         s=(ll)s*i%mo;        
36     cout<<(ll)f[m]*inv(s,mo)%mo<<endl;
37     return 0;
38 }
View Code

 

posted @ 2017-10-18 18:46 Bleacher 阅读(...) 评论(...) 编辑 收藏