P3214 [HNOI2011] 卡农

P3214 [HNOI2011] 卡农

很好的计数题呢,代码很短很 atcoder 的题。

思路

考虑 dp。

\(dp_i\) 表示有 \(i\) 个片段的合法种数。这是不好直接算的。我们注意到假设我们随意选了 \(i - 1\) 个不重区间,为了让每个音阶都被演奏偶数次,我们第 \(i\) 个集合是可以确定的,这样子的方案数应为:

\[\binom{2 ^ n - 1}{i - 1} \]

\(2 ^ n - 1\) 表示 \(n\) 个音节构成的非空集合数。于其中选 \(i\) 个即为答案。

显然这个并不是 \(dp_i\) 真正的值。

首先,有可能第 \(i\) 个集合(即那个通过前 \(i - 1\) 个集合确定的集合)可能为空集,这样子的方案数字显然是 \(dp_{i - 1}\)

其次,有可能第 \(i\) 个集合可能与前 \(i - 1\) 个集合有重复,这样的贡献为 \(dp_{i - 2} \times (2 ^ n - 1 - (i - 2))\),其中 \(2 ^ n - 1 - (i - 2)\) 为第 \(i\) 个集合可能的方案数。

最后,这样子我们考虑到答案应为无序的,那么每种方案数这样是被算了 \(i\) 次的。

综上,

\[dp_i = (\binom{2 ^ n - 1}{i - 1} - dp_{i - 1} - dp_{i - 2} \times (2 ^ n - 1 - (i - 2))) \times \frac{1}{i} \]

做完了。

代码

注意到无法预处理 \(2 ^ n - 1\) 的阶乘,所以说组合数需要单独递推求。

void ACehomoxue() {
    cin >> n >> m;
    dp[0] = 1, dp[1] = 0;
    binomm = 1;
    for(int i = 2; i <= m; i++) { 
        binomm = binomm * Mod(ksm(2, n) - i + 1) % mod * ksm(Mod(i - 1)) % mod;
        dp[i] = Mod(binomm - dp[i - 1] - dp[i - 2] * (ksm(2, n) - 1 - (i - 2)) % mod) * ksm(i) % mod;
    }
    cout << dp[m], el;
}
posted @ 2026-04-21 17:39  ACehomoxue  阅读(4)  评论(0)    收藏  举报