P3214 [HNOI2011]卡农
题意
在集合 \(\{1,2,\cdots,n\}\) 中选出
\(m\) 个非空子集满足:
- 不存在完全相同的两个集合;
- 每个元素在所有集合中出现次数之和为偶数。
思路
考虑转移,利用容斥方法进行 Dp。设 \(f_i\) 表示选了 \(i\) 个集合满足条件的方案数:
- 首先,如果确定了前 \(i-1\)
个集合,那么为了满足上面第二个限制这个位置选的集合一定是固定的。前 \(i-1\) 个集合的选择方案数是 \(A_{2^n-1}^{i-1}\)。
- 上面的方案中有选择了空集的方案。选择空集当且仅当前 \(i-1\) 个集合已经是合法方案了,即有 \(f_{i-1}\) 个是多计算的,减去即可。
- 还有选择集合相同的方案数。考虑若有相同的集合那么去掉这两个集合剩下的也是合法的,即
\(f_{i-2}\)。有 \(i-1\) 个位置和 \(2^n-1-(i-2)\) 种取法(减去空集和不同的
\(i-2\) 个集合),所以总共有 \(f_{i-2}*(i-1)*(2^n-1-(i-2))\)
种,减去即可。
转移式为: \[
f_i=A_{2^n-1}^{i-1}-f_{i-1}-f_{i-2}*(i-1)*(2^n-1-(i-2))
\] 边界条件为 \(f_0=1\),\(f_1=0\)。
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include<iostream> #include<cstdio> #include<algorithm> #include<cctype> #include<cstring> #include<cmath> using namespace std; inline int read(){ int w=0,x=0;char c=getchar(); while(!isdigit(c))w|=c=='-',c=getchar(); while(isdigit(c))x=x*10+(c^48),c=getchar(); return w?-x:x; } namespace star { const int maxn=1e6+10,mod=1e8+7; int n,m,pow=1,A[maxn],f[maxn]; inline int fpow(int a,int b){int ans=1;for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) ans=1ll*ans*a%mod;return ans;} inline void work(){ n=read(),m=read(); for(int i=1;i<=n;i++) pow=(pow<<1)%mod; pow=(pow-1+mod)%mod; A[0]=1;for(int i=1;i<=m;i++) A[i]=1ll*A[i-1]*((pow-i+1+mod)%mod)%mod; f[0]=1,f[1]=0; for(int i=2;i<=m;i++) f[i]=(A[i-1]-f[i-1]+mod-1ll*f[i-2]*(i-1)%mod*(pow-i+2+mod)%mod+mod)%mod; int res=1; for(int i=1;i<=m;i++) res=1ll*res*i%mod; printf("%lld\n",1ll*f[m]*fpow(res,mod-2)%mod); } } signed main(){ star::work(); return 0; }
|
闲话
我搜了下,音阶的意思是从主音到主音的连续音符段,所以一个音阶是若干音符的集合(大概)
所以小余大概是把一个音符分成了一个音阶(