洛谷题单指南-组合数学与计数-P3214 [HNOI2011] 卡农

原题链接:https://www.luogu.com.cn/problem/P3214

题意解读:从数字1~n组成的所有非空子集中,选m个子集组成集合T,要求集合T里所有子集的数字都出现偶数次,请所有T的方案数。

解题思路:

数字1~n组成的非空子集一共有2n-1个(每个数字有选或不选两种,排除全不选)

如果不考虑数字出现偶数次的限制,总的方案数为C(2n-1,m),关键要思考如何排除掉出现偶数次的情况。

这里借助于递推:

设f[i]表示选了前i个子集且所有数都出现偶数次的方案数

考虑选第i个子集的时候,方案是确定的,因为前i-1如果已经选好,所有数字的奇偶性是确定的,如果不考虑奇偶性,选前i-1个子集的所有方案数是C(2n-1,i-1)

选第i个子集有两种不合法的可能:

1、选空集合,前i-1个子集中数字都已经出现偶数次

由于C(2n-1,i-1)里包括了选空集的情况,前i-1个集合数字是偶数个的时候,第i个为空集,一共有f[i-1]种

2、第i个子集与前i-1个子集有重复的情况

首先,有i-2个子集是不重复的,还有2个子集重复,重复的子集有2n-1-(i-2)=2n-i+1种选择,i-2个子集是合法的方案数是f[i-2],因此第i个子集与前i-1个子集有重复的情况一共有f[i-2]*(2n-i+1)种

这时,得到选前i个子集的总方案数是C(2n-1,i-1)-f[i-1]-f[i-2]*(2n-i+1)

去掉不合法的情况之后,对于i个子集都确定的情况下,第i个子集一共有i种选法,方案数里已经包括了这i种选法,因此最终:

f[i] = ( C(2n-1,i-1)-f[i-1]-f[i-2]*(2n-i+1) ) / i

C(2n-1,i-1)可以通过展开后阶乘逆元在递推中来计算,C(2n-1,i-1) = (2n-1)*...*(2n-i+1) / (i-1)!

初始时f[0]=f[1]=f[2]=0,从i=3开始求,C(2n-1,3-1) = (2n-1)*...*(2n-2) / 2!,

下一次递推C(2n-1,4-1) = C(2n-1,3-1) * (2n-3) / 3,因此下一次C(2n-1,i-1) 要*(2n-i) / i

除以i变为乘以i模mod的逆元。

100分代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int N = 1000005, MOD =  1e8 + 7;
LL f[N], C;
LL n, m;

LL ksm(LL a, LL b, LL mod)
{
    LL res = 1;
    while(b)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main()
{
    cin >> n >> m;
    //C(2^n-1,3-1) = (2^n-1)*...*(2^n-2) / 2!
    LL t = ksm(2, n, MOD);
    C = (t - 1) * (t - 2) % MOD * ksm(2, MOD - 2, MOD) % MOD; 
    for(int i = 3; i <= m; i++)
    {
        //f[i] = ( C(2^n-1,i-1)-f[i-1]-f[i-2]*(2^n-i+1) ) / i
        f[i] = C - f[i - 1] - f[i - 2] * (t - i + 1) % MOD;
        f[i] = (f[i] % MOD + MOD) % MOD * ksm(i, MOD - 2, MOD) % MOD;
        //C = C * (2^n-i+1) / i
        C = C * (t - i) % MOD * ksm(i, MOD - 2, MOD) % MOD;
    }
    cout << f[m];
    return 0;
}

 

posted @ 2025-12-01 12:34  hackerchef  阅读(4)  评论(0)    收藏  举报