题解:卡农(组合计数+DP)

题面

image

简化一下,有 \(3\) 个限制:

  1. 不能是空集。
  2. 每个元素出现的次数必须为偶数。
  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\) 的第一道黑题,纪念一下。
posted @ 2024-03-05 17:47  卡布叻_周深  阅读(32)  评论(3编辑  收藏  举报