[HNOI2011] 卡农 题解
题目传送门
提交记录😭😭😭
[HNOI2011] 卡农
题目描述
众所周知卡农是一种复调音乐的写作技法,小余在听卡农音乐时灵感大发,发明了一种新的音乐谱写规则。
他将声音分成 \(n\) 个音阶,并将音乐分成若干个片段。音乐的每个片段都是由 \(1\) 到 \(n\) 个音阶构成的和声,即从 \(n\) 个音阶中挑选若干个音阶同时演奏出来。
为了强调与卡农的不同,他规定任意两个片段所包含的音阶集合都不同。同时为了保持音乐的规律性,他还规定在一段音乐中每个音阶被奏响的次数为偶数。
现在的问题是:小余想知道包含 \(m\) 个片段的音乐一共有多少种。
两段音乐 \(a\) 和 \(b\) 同种当且仅当将 \(a\) 的片段重新排列后可以得到 \(b\)。例如:假设 \(a\) 为 \(\{\{1,2\},\{2,3\}\}\),\(b\) 为 \(\{\{2,3\},\{1,2\}\}\),那么 \(a\) 与 \(b\) 就是同种音乐。
答案对 \(10^8+7\) 取模。
输入格式
仅一行两个正整数 \(n,m\)
输出格式
输出一行一个整数表示答案。
样例 #1
样例输入 #1
2 3
样例输出 #1
1
提示
【数据范围】
对于 \(20\%\) 的数据,\(1\le n,m \le 5\);
对于 \(50\%\) 的数据,\(1\le n,m \le 3000\);
对于 \(100\%\) 的数据,\(1\le n,m \le 10^6\)。
【样例解释】
音乐为 \(\{\{1\},\{2\},\{1,2\}\}\)
呃呃
打了半天一直RE20分 发布帖子求助后得到思路www🥵🥵🥵
思路、
一个集合的非空集合共有\(2^n-1\)个
简化题意就是 \({1,2,3,4,...,n}\)元素构成的集合里面取\(m\)个子集使之满足
(1)所有选出的\(m\)个子集都不能为空。
(2)所有选出的\(m\)个子集中,不能存在两个完全一样的集合。
(3)所有选出的\(m\)个子集中,\(1\)到\(n\)每个元素出现的次数必须是偶数。
先考虑没有三个限制条件的情况
在\(2^n-1\)个子集里取\(m\)个子集共有\(C_{2^n-1}^{m}\)种可能
然后需要减去不符合三个条件的情况:
使用二进制思想考虑
如果每个元素出现的次数都是偶数,那么它们的异或一定是0
例: \(114514 xor 114514xor1919810xor1919810=0\)
先考虑第三个条件
(3).1到\(n\)每个元素出现的次数必须是偶数
考虑第\(i\)个元素 \(dp[i]\)表示取第\(i\)个集合出现的方案数
我们发现,如果前\(m-1\)个集合确定了,那么第\(m\)个集合也是确定的
因为对于任意一个元素\(op\),在前\(m-1\)个集合内如果出现次数是奇数,为了满足条件\(3\),它在第\(m\)个集合内一定出现,满足偶数。
如果前\(m-1\)个集合内如果出现次数是偶数,为了满足条件\(3\),它在第\(m\)个集合内一定不出现,满足偶数
所以 加上限制\(3\)后,方案数为\(C_{2^n-1}^{i-1}\)
🥰
再考虑第一个条件
\((1).\)所有选出的\(m\)个子集都不能为空
对于\(dp[i]\),思考\(dp[i-1]\)的含义,根据上文定义,可知\(dp[i-1]\)意思是取第\(i-1\)个集合出现的方案数,此时它的元素一定有偶数个
则最后一个集合为了满足条件\(3\),一定是空集,不合法
对于\(i\),我们可以发现出现空集的方案数就是\(dp[i-1]\),因为它就等于前\(i-1\)个集合中出现次数为偶数的方案数
或者前\(i-1\)个子集的异或为0,则有\(dp[i-1]\)种方案
🥰
考虑第二个限制条件
我们需要减去第\(i\)个子集和前面某个子集相同的情况
如果我们把相同的两个子集都删去,那么剩下的\(i-2\)个子集肯定合法,即方案数\(dp[i-2]\),对于第\(i\)个子集,因为删去了两个相同的两个子集,\(i\)的方案数为\(2^n-1-(i-2)\),\(i-2\)就是前面合法的位置
所以重复的方案数为:\(dp[i-2]*(2^n-1-(i-2))\)
考虑最后一个问题
有些 方案 被重复计算了怎么办?
最难思考的地方呜呜呜
我们随便找一个集合\(op\),假设这是最后一个确定的集合,这个集合有\(i\)种选择,所以每种方案重复枚举了i次
对于这个问题,我们只需考虑一个合法的 \(op\) 有多少种可能可以用这种方法得到。我们随便在 \(op\) 里面选一个集合,它可以成为最后被确定的集合来计算一次 \(op\),而这个集合显然有 \(|op|=i\) 种选择,于是我们把答案除以 i 即可。
即\(dp[i]\)最后要\(×i\)の逆元
总结



考虑化简\(C_{2^n-1}^{i-1}\)
其他题都是预处理出阶乘和阶乘的逆元再用函数输出
for(register int i=1;i<=1e6+5;i++){
x[i]=x[i-1]*i%mod;
y[i]=y[i-1]*(qpow(i,mod-2))%mod;
// z[i]=qpow(i,mod-2)%mod;
}
inline int C(int n,int m){
if(n<m) return 0;
return ((x[n]%mod*y[n-m]%mod)%mod*y[m]%mod)%mod;
}
但是我们可以发现 组合数不能这么算的。你代码中的 \(op\) 的值域是\(10^8+7\),远远超出了你预处理的范围 ,这里注意到,\(op\) 是固定的,而 \(i-1\) 比较小,所以可以直接递推
递推
\(C_{2^n-1}^{i-1}\Rightarrow\)
\(C_{op}^{2}=\frac{(op)×(op-1)}{2}\Rightarrow\)
\(C_{op}^{3}=\frac{C_{op}^{2}×(op-2)}{3}\Rightarrow\)
\(C_{op}^{4}=\frac{C_{op}^{3}×(op-3)}{4}\Rightarrow\)
\(......\)
\(C_{op}^{i}=\frac{C_{op}^{i-1}×(op-(i-1))}{i}\)
递推完了!
然后注意一下除法需要用费马小定理处理逆元,因为\(10^8+7\)太大,不能预处理,所以直接在使用的时候用快速幂处理
\(C\)的初值为
int c=((((op-1)*niyuan(2)))%mod*((op)%mod))%mod;
最后附上AC代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace Testify{
inline int read(){
int f(1),x(0);
char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
return f*x;
}
inline void Write(int x){
if(x>9) Write(x/10);
putchar(x%10+'0');
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
Write(x);
putchar('\n');
}
}
using namespace Testify;
const int mod=1e8+7;
const int N=1e6+114514;
int n,m,dp[N],x[N],y[N];
inline int qpow(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
inline int C(int n,int m){
if(n<m) return 0;
return ((x[n]%mod*y[n-m]%mod)%mod*y[m]%mod)%mod;
}
inline int niyuan(int n){
return qpow(n,mod-2)%mod;
}
signed main(void){
n=read(),m=read();
x[0]=y[0]=1;
int op=qpow(2,n)-1;
op=((op%mod)+mod)%mod;
// cerr<<"op: "<<op<<endl;
if(op<m){
write(0);
return 0;
}
// for(register int i=1;i<=op+1;i++){
// x[i]=x[i-1]*i%mod;
// y[i]=y[i-1]*(qpow(i,mod-2))%mod;
// }
// int aa=x[op];
// int c=(x[op]%mod*y[2]%mod*y[op-2]%mod)%mod;
// int c=op*(op+mod-1)%mod+qpow(2,mod-2)%mod;
int c=((((op-1)*niyuan(2)))%mod*((op)%mod))%mod;
// cerr<<"C: ";
// clog<<C(op,2)<<endl;
dp[1]=0,dp[2]=0;
for(register int i=3;i<=m;i++){
dp[i]=((c-dp[i-1]-(dp[i-2]*(op-(i-2))))%mod+mod)%mod*niyuan(i)%mod;
// c=(c%mod*y[i]%mod*y[op-i]%mod)%mod;
// c=c*(op-i+1+(int)mod)%mod*qpow(i,mod-2)%mod;
// c=(c*(op-(i-1))*((qpow(i-1,mod-2)%mod)))%mod;
c=((c*(((op-(i-1)))%mod+mod)%mod)%mod*niyuan(i))%mod;
// c=c*(op-(i-1))
}
write(dp[m]);
return 0;
}


浙公网安备 33010602011771号