[luogu p3214] [HNOI2011] 卡农
作者:互联网
\(\mathtt{Link}\)
P3214 [HNOI2011] 卡农 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
\(\mathtt{Description}\)
在集合 \(S = {1, 2, \ldots, n}\) 中,选出 \(m\) 个子集,使得
- 无空集
- 选定的 \(m\) 个集合两两不同
- 选定的 \(m\) 个集合中,\(1\) 到 \(n\) 中每个元素出现次数必为偶数。
求选定方案数。答案对 \(10^8+7\) 取模。
\(\mathtt{Data} \text{ } \mathtt{Range} \text{ } \mathtt{\&} \text{ } \mathtt{Restrictions}\)
- \(1 \le n, m\le 10^6\)
\(\mathtt{Solution}\)
为什么不 % 1e9??为什么为什么为什么
本题最棘手的部分是第三个性质。
那么先不考虑第三个性质,只考虑前两个,容易发现一共可以有 \(2 ^n - 1\) 个非空集合可选,选 \(m\) 个。显然答案是 \(\dbinom{2^n - 1}{m}\)。
再考虑第三个性质,发现在这种情况下,如果选定了 \(m - 1\) 个集合已经确定,第 \(m\) 个集合实质上已经确定。因为在前 \(m - 1\) 个集合中出现了奇数次的元素一定要在这第 \(m\) 个集合中,出现了偶数次的元素一定不能在这第 \(m\) 个集合中,否则便不满足元素出现次数为偶数。
那么答案就是简单的 \(\dbinom{2^n - 1}{m - 1}\) 吗?很遗憾的发现,第三个性质成功维护,而第一个和第二个性质没维护上。也就是说:如果新的集合是空集,或者新的集合与前几个集合重复,这些情况还需要处理。
分别处理。
首先对于新的集合是空集的情况,其实就是不算新集合,前面 \(m - 1\) 个集合满足元素次数出现偶数次的情况。这提示我们递推:如果设 \(f_i\) 为 \(i\) 个集合中满足选法的方案数,事实上它是跟 \(f_{i - 1}\) 有关的,即新的集合是空集的情况有 \(f_{i - 1}\) 种。
顺着这个继续考虑重复。如果目前要选的第 \(m\) 个集合与前 \(m - 1\) 个集合中有一对重复的,考虑到去掉这两个集合后,剩下的 \(m -2\) 个集合正好有 \(f_{i - 2}\) 种方案。(因为原先是满足第三条性质的,去掉两个相同的集合,显然元素出现次数仍然都是偶数,这个性质仍然在维护)。而这两个集合则可以有 \(2^n - 1 - (i - 2)\) 种选法,所以新的集合与原来有所重复的情况一共有 \(f_{i - 2} \times (2^n - i + 1)\) 种。
注意到答案中求的是无序方案数,所以每一次递推中每种方案都重复了 \(i\) 次。我们需要再除以 \(i\).
总递推式:
\[f_i = \dfrac{\dbinom{2^n - 1}{i - 1} - f_{i - 1} - f_{i - 2} \times (2^n - i +1)}{i} \]\(\mathtt{Time} \text{ } \mathtt{Complexity}\)
\(\operatorname{O}(n\log n)\)
\(\mathtt{Code}\)
/*
* @Author: crab-in-the-northeast
* @Date: 2022-04-22 23:37:20
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-04-23 00:22:39
*/
#include <iostream>
#include <cstdio>
#define int long long // 好习惯,此处 % 葵 %%%%%%%
//但是这样我真的很烦你知道吗?int所有高亮都变成了紫色,跟 #include 一样
//无语
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
flag = false;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if (flag)
return x;
return ~(x - 1);
}
const int mod = 1e8 + 7;
inline int pow(int a, int b = mod - 2) {
int s = 1;
for (; b; b >>= 1, (a *= a) %= mod)
if (b & 1)
(s *= a) %= mod;
return s;
}
signed main() {
int n = read(), m = read();
int S = pow(2, n) - 1;
int C = S * (S - 1) % mod * pow(2) % mod;
if (m <= 2) {
puts("0");
return 0;
}
int f = 0, g = 0, h = 0;
for (int i = 3; i <= m; ++i) {
f = (C - g - h * (S - i + 2 + mod) % mod + mod) % mod * pow(i) % mod;
(C *= (S - i + 1 + mod) * pow(i) % mod) %= mod;
h = g;
g = f;
}
printf("%lld\n", f);
return 0;
}
不错的题2!
标签:ch,int,luogu,偶数,HNOI2011,text,集合,mathtt,卡农 来源: https://www.cnblogs.com/crab-in-the-northeast/p/luogu-p3214.html