[BZOJ 2839] 集合计数

看见计数想容斥(下面部分可略过直接跳到求含特定 \(k\) 个数的集合)

首先求一个集合的子集个数可由 \(2 \cdot 2 \cdot 2 \cdot 2...\) (包含 \(n\)\(2\))得到,其中每个二表示选或者不选本个元素的两种情况。

即一个有 \(n\) 个元素的集合存在 \(2^n\) 个子集

然后同理可得从 \(2^n\) 个子集中选交集的方案数为 \(2 \cdot 2 \cdot 2 \cdot 2...\) (共包含 \(2^n\)\(2\)) (同子集选元素的方式)

所以总数为 \(2^{2^n}\) 个选择方式。


现在求含特定 \(k\) 个数的集合

\(n\) 个数中找 \(k\) 个数的方案数是 \(C^k_n\) ,而在剩下的 \(n-k\) 个数中找子集的方案数为 \(2^{n-k}\)

这里面含有空集,实际空集指的是正好只含这K个数的集合。

所以在这 \(2^{n-k}\) 中选择的方案数是 \(2^{2^{n-k}}-1\) ,其中减一是因为不能不选任何一个子集。(起码要选 \(2^{n-k}\) 个集合中任意一个子集,若一个集合都不选,则不存在有 \(k\) 个数的集合)。

完成了上面的推导后,我们显然会发现在 \(2^{2^{n-k}}-1\) 个方案中有的集合交起来并不只有 \(k\) 个,而是最少含有 \(k\) 个数相交的集合的方案数。

下面,新的问题又出现了。


现在要用容斥原理将多余的交集方案数减去

看个图先

image

显然我们根据容斥定理可得,若要求全集只要将并集减去至少交奇数次的集合加上至少交偶数次的集合

那本题的容斥定理怎么得到呢???。

若我们将 \(n-k\) 个数的集合作为图中的全集,其中每个圈(也就是集合)表示除了特定 \(k\) 个数外交集多包含了的数 \(i\) ,则图中每个大圈(如 {\(1,2,5,4\)} , {\(2,3,5,6\)}, {\(4,5,6,7\)})表示交集多了一个数的情况,而两个大圈交起来的地方 (如 {\(2,5\)}, {\(5,6\)}, {\(4,5\)})表示交集多了两个数的情况;三个大圈交起来的地方 ({\(5\)})表示交集多了3个数的情况......那么此时就显然可以用容斥原理做了。

接下来我们开始推容斥的式子

交集除了\(k\) 外多了 \(i\) 个元素,则所多的元素所表示的集合有 \(C^i_{n-k}\) 种可能(从 \(n-k\) 个元素里选 \(i\) 个元素), 而剩下的数的组合起来又有 \(2^{2^{n-k-i}}-1\) 种方案, 则可以得到多 \(i\) 个元素对应的总方案是 \(C^i_{n-k}\cdot(2^{2^{n-k-i}}-1)\)

最终我们推得

\[ans=C^k_n \cdot (2^{2^{n-k}}-1) + C^k_n \cdot \sum^{n-k} _ {i=1} (-1)^i \cdot C^i_{n-k} \cdot (2^{2^{n-k-i}}-1) \]

(后面容斥的式子要乘上 \(C^k_n\) ,是因为后面的容斥是在 \(n\) 个数里选 \(k\) 个数的情况下再在 \(n-k\) 个数里选 \(i\) 个数。)
可合并成

\[ans=C^k_n \cdot \sum^{n-k} _ {i=0} \cdot (-1)^i \cdot C^i_{n-k} \cdot (2^{2^{n-k-i}}-1) \]

(PS:人生第一道真正的容斥原理,真的恶心坏我辣)

代码实现

\(2\) 的次幂可以用递推来实现,我们可以从 \(n-k\) 开始逆推

\(i\)\(n-k\) 时 $ \ \ \ \ \ \ \ \ \ $ 显然是 \((2^{2^{n-k-(n-k)}}-1)\) 也就是 \(2^{2^0}-1\)\(2^1-1\)
\(i\)\(n-k-1\) 时 $ \ \ $ 显然时 \((2^{2^{n-k-(n-k-1)}}-1)\) 也就是 \(2^{2^1}-1\)\(2^2 - 1\)
\(i\)\(n-k-2\) 时 $ \ \ $ 显然时 \((2^{2^{n-k-(n-k-2)}}-1)\) 也就是 \(2^{2^2}-1\)\(2^4 - 1\)
\(i\)\(n-k-3\) 时 $ \ \ $ 显然时 \((2^{2^{n-k-(n-k-3)}}-1)\) 也就是 \(2^{2^3}-1\)\(2^8 - 1\)
\(i\)\(n-k-4\) 时 $ \ \ $ 显然时 \((2^{2^{n-k-(n-k-4)}}-1)\) 也就是 \(2^{2^4}-1\)\(2^{16} - 1\)

呦呵,发现规律辣!!!!!我们的快速幂的芝士派上用场辣!!!!!

Code

#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>

using namespace std;

const long long mod = 1000000007;
long long n, N, k, ans = 0;
long long inv[1000001], fac[1000001];

long long GetC(long long a, long long b) {
	if (a < b) return 0;
	return fac[a] * inv[a - b] % mod * inv[b] % mod;
}

int main() {
	cin >> N >> k;
	fac[0] = fac[1] = inv[0] = inv[1] = 1;
	for (int i = 2; i <= N; ++ i) {
		fac[i] = fac[i - 1] * i % mod;
		inv[i] = (mod - mod / i) * inv[mod % i] % mod;
	}
	for (int i = 2; i <= N; ++ i) {
		inv[i] = inv[i - 1] * inv[i] % mod;
	}
	n = N - k;
	int flag = (n & 1) ? -1 : 1;
	long long p, power = 2;
	for (int i = n; i >= 0; -- i) {
		p = (power - 1 + mod) % mod;
		ans = (ans + flag * GetC(n, i) % mod * p % mod) % mod;
		power = power * power % mod;
		flag = -flag; 
	}
	ans = ans * GetC(N, k) % mod;
	cout << (ans + mod) % mod;
	return 0;
} 
posted @ 2023-06-14 20:04  觉清风  阅读(92)  评论(4编辑  收藏  举报