题解:卡农(组合计数+DP)
题面
简化一下,有 \(3\) 个限制:
- 不能是空集。
- 每个元素出现的次数必须为偶数。
- 不能出现两个相同的集。
思路
首先不用状压,但是需要 \(DP\) ,因为 \(n\) 范围过大用状压内存放不下,不然本来状压很好用的。
考虑数学方法 \(+DP\) 。
限制 \(1\)
因为不能有空集,所以可选集合数为 \(2^n-1\) ,接下来为了方便,设\(n=2^n-1\)。
此时显然方案数为 \(\text{C}_n^m\) 。
限制 \(2\)
对于最后一个集合,即集合 \(m\) ,发现当前 \(m-1\) 个集合已经选定的情况下,集合 \(m\) 就确定了。
-
证明:
若前面 \(m-1\) 个集合中,元素 \(x\) 出现了奇数次,则集合 \(m\) 中,\(x\) 必须出现;
同理的,若元素 \(x\) 在前 \(m-1\) 个集合中出现了偶数次,则集合 \(m\) 中,\(x\) 必定不出现。
所以对于此时,最后的合法方案数就变成了 \(\text{C}_n^{m-1}\) 。
同时考虑限制 \(1\) 的影响
其集合 \(m\) 不可为空集,但同时通过上述的证明,若前 \(m-1\) 个集合中,所有元素出现的次数都为偶数,那么集合 \(m\) 只能为空集,则结论不再成立。
于是需要考虑解决方案——使用 \(DP\) 。
定义 \(dp[i]\) 表示 \(m=i\) 时的合法方案数。
对于上面的问题,发现,即若前 \(m-1\) 个集合已经构成了合法状态(满足限制 \(1,2\) ),则集合 \(m\) 一定为空集,即这 \(m\) 个集合无法构成合法状态。
那么此时合法状态数就变成了 \(\text{C}_n^{m-1}-dp[m-1]\) 。
转移方程也出来了,\(dp[i]=\text{C}_n^{i-1}-dp[i-1]\) 。
-
同时通过此处的证明,不难发现,当 \(m\leq 2\) 时,合法方案数一定为 \(0\) 。
当 \(m=1\) 时,显然,因为不能是空集,所以出现的元素,其数量必定为 \(1\) 。
对于 \(m=2\) ,因为限制 \(3\) 中规定不可出现同集,所以集合 \(1\) 中出现的元素在集合 \(2\) 中一定不能出现。
即 \(dp[1]=dp[2]=0\) 。
限制 \(3\)
对于前 \(i\) 个集合中,存在某个集合与集合 \(i\) 相同,目的就是减去所有此情况下的方案数。
那么去掉这两个相同的集合,则剩下 \(i-2\) 个集合一定是合法的。
-
证明:
此时若不考虑限制 \(3\) ,则这 \(i\) 个集合构成合法状态,即所有元素出现次数一定为偶数次。
那么当前存在集合 \(x\) 与集合 \(y\) 为相同的,只考虑这两个集合中出现的每个元素各出现 \(2\) 次。
所以去掉这两个集合后,根据 \(偶数-偶数=偶数\) ,所剩 \(i-2\) 个集合中,每个元素出现的次数仍为偶数,为合法状态。
那么对于集合 \(i\) ,它与该 \(i-2\) 个集合均不相同,同时这 \(i-2\) 个集合互不相同,所以集合 \(i\) 可选的集合还剩 \(n-(i-2)\) 种。
由此,此时的方案数为 \(dp[i-2]\times (n-(i-2))\) 。
那么减掉这些方案,\(dp[i]=\text{C}_n^{i-1}-dp[i-1]-dp[i-2]\times (n-(i-2))\) 。
隐藏性质
对于当前所求出来的,并不满足两种方案一定不是同种音乐(见题目描述)。
比如 \(a\{\{1,2\},\{2,3\}\};b\{\{3,2\},\{2,1\}\}\) 为同种音乐。
那么思考如何解决该问题。
对于前 \(i\) 个集合,每一个集合都可以为最后一个确定的集合(即集合 \(i\) ),那么这样的话就有 \(i\) 种情况。
所以 \(dp[i]=\dfrac{\text{C}_n^{i-1}-dp[i-1]-dp[i-2]\times (n-(i-2))}{i}\) 。
一个难理解的地方,为什么只考虑位置 \(i\) 就可以了,不应该是 \(\text{A}_i^i\) 种可能的排列吗?
思考 \(DP\) 的性质,他是一层一层推过来的,用上面的转移方程的话,考虑 \(dp[i]\) 时,\(dp[i-1]\) 已经确定了位置 \(i-1\) ,再往前推,\(dp[i-2]\) 已经确定了位置 \(i-2\) …… \(dp[1]\) 已经确定了位置 \(1\) ,由此一直推过来,所有位置都是确定的。
好的问题解决。
复杂度处理
逆元
可以预处理也可以费马小定理,因为除数没有 \(>m\) 的,\(m\leq 1e6\) 。
\(\text{C}\)
每次都先根据公式求显然会 \(T\) ,考虑递推。
\(\text{C}_n^i=\dfrac{n!}{i!(n-i)!}\) ,所以 \(\text{C}_{n}^{i-1}=\dfrac{n!}{(i-1)!(n-i+1)!}\) 。
所以 \(\text{C}_n^i=\text{C}_n^{i-1}\times \dfrac{n-i+1}{i}\) 。
又发现 \(n\) 始终都是 \(2^n-1\) ,所以从 \(\text{C}^1_{2^n-1}\) 递推到 \(\text{C}^m_{2^n-1}\) 即可。
总体复杂度
-
若预处理逆元,复杂度为 \(O(m)\) 。
-
若用费马小定理 \(+\) 快速幂,复杂度 \(O(m\times log(m))\) 。
代码如下
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e6+10,P=1e8+7;
template<typename Tp> inline void read(Tp&x)
{
x=0;register bool z=true;
register char c=getchar();
for(;c<'0'||c>'9';c=getchar()) if(c=='-') z=0;
for(;'0'<=c&&c<='9';c=getchar()) x=(x<<1)+(x<<3)+(c^48);
x=(z?x:~x+1);
}
int n,m,f[N],maxx,c[N];
int qpow(int a,int b)
{
int ans=1;
while(b)
{
if(b&1) (ans*=a)%=P;
a=(a*a)%P;
b>>=1;
}
return ans%P;
}
void C(int m)
{
c[1]=maxx;
for(int i=2;i<=m;i++)
c[i]=(i<=maxx)?(((c[i-1]*(maxx-i+1))%P)*qpow(i,P-2))%P:0;
}
signed main()
{
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
read(n),read(m);
maxx=(qpow(2,n)-1+P)%P;
C(m);
f[1]=f[2]=0;
for(int i=3;i<=m;i++)
f[i]=(((c[i-1]-f[i-1]-((f[i-2]*(maxx-i+2))%P))*qpow(i,P-2))%P+P)%P;//此处注意这个+P%P,为了防止出现负数。
cout<<f[m];
}
- 本蒟蒻 \(A\) 的第一道黑题,纪念一下。