HNOI2011 卡农
讲个笑话:我的做法是 \(O(n\log \ n)\) 的
Description
有对于一个给定的集合 \(S=1...n\) ,选取其中 \(m\) 个子集要求子集非空,不能相同,并且每个出现过的元素出现次数是偶数
\(n,m \le 10^6\)
Solution
这个题要求组合的方案,先转化一步:求排列的方案然后除以阶乘
状态:\(f_i\) 为取 \(i\) 个集合的方案
之后发现一个性质:我们确定前 \(i-1\) 个集合,最后一个就被自然确定了
(补全原来没有拿成偶数的元素即可)
这样我们发现取 \(m\) 个集合的方案是:\(A_{2^{n-1}}^{i-1}\)
这样我们发现可能不太对
\(1.\) 最后一个集合是空集,即原来取的方案已经满足所有的元素出现的个数为偶数
解决方案:直接\(f[i]-=f[i-1];\)
\(2.\) 最后一个集合和原来的集合是重复的
最后一个集合可能和原来的 \(i-1\) 个的重复,此时我们拿出那两个重复的集合,还剩余 \(i-2\) 个
取\(i-2\) 的方案是有 \(f_{i-2}\) 个
重复的集合的可能有\(sum(2^n-1)-exist(i-2)\) 括号里的是真值
理解就是总的个数和最后剩下没有重复的个数
重复集合的位置有 \(i-1\) 种(有序计数)
预处理阶乘逆元和排列数即可
讲述前面的那个笑话:考虑到我闲的就把 \(2^n\) 求了 \(n\) 次
Code
#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace yspm{
inline int read()
{
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=1e6+10,mod=1e8+7;
int inv,p,f[N],n,m,a[N];
inline int ksm(int x,int y)
{
int res=1; for(;y;y>>=1,(x*=x)%=mod) if(y&1) (res*=x)%=mod;
return res;
}
int num;
signed main()
{
n=read(); m=read(); inv=1;
for(int i=1;i<=m;++i) inv*=i,inv%=mod; inv=ksm(inv,mod-2);
a[0]=1; for(int i=1;i<=m;++i) a[i]=a[i-1]*((ksm(2,n)-i+mod)%mod)%mod;
f[0]=1;
for(int i=2;i<=m;++i)
{
f[i]=(a[i-1]-f[i-1]+mod)%mod;
f[i]-=f[i-2]*(i-1)%mod*((ksm(2,n)-1-(i-2)+mod)%mod)%mod;
(f[i]+=mod)%=mod;
}
cout<<f[m]*inv%mod<<endl;
return 0;
}
}
signed main(){return yspm::main();}
Review
组合计数不好求的时候可以考虑转排列求阶乘
再一个就是数学的分类讨论一定要细致求好