Bzoj2839:集合计数
题意:
一个有 n n n个元素的集合有 2 n 2^n 2n个不同子集(包含空集),现在要在这 2 n 2^n 2n个集合中取出若干集合(至少一个),使得它们的交集的元素个数为 k k k,求取法的方案数,答案模1000000007。
题解:
可以先枚举选取哪k个元素,一共有 c n k c_{n}^{k} cnk种情况,选取 k k k个之后还剩下 n − k n-k n−k个数字,对于剩下的数字每个可以选或不选一共有 2 n − k 2^{n-k} 2n−k种情况,即这么多种集合,对于每一个集合又可以选或不选,那么一共有 2 2 n − k 2^{2^{n-k}} 22n−k种选法,但是必须要有一个集合附带上前面选取的 k k k个元素才可以,因此要减去空集那种情况,所以最终的方案数就是 c n k × ( 2 2 n − k − 1 ) c_{n}^{k}\times(2^{2^{n-k}} - 1) cnk×(22n−k−1) ,但是我们发现,当前并不是交集恰好为 k k k的情况,剩下的集合中是可能会有相同元素的,这个式子的实际含义是交集至少为 k k k的方案数。
怎么求呢?首先设 g ( i ) 表 示 交 集 元 素 恰 好 是 i 的 方 案 数 g(i)表示交集元素恰好是i的方案数 g(i)表示交集元素恰好是i的方案数,我们只需要让方案数至少是k的减去大于k的恰好的方案数即可,也就是 c n k × ( 2 2 n − k − 1 ) − ∑ i = k + 1 n g ( i ) c_{n}^{k}\times(2^{2^{n-k}} - 1) - \sum_{i = k + 1}^{n}g(i) cnk×(22n−k−1)−∑i=k+1ng(i),可以考虑倒着跑一遍每次到k的时候做一次当前这个个公式,复杂度为 O ( n 2 ) O(n^2) O(n2)会T,是可以优化的。
就用到了容斥原理,我们发现对于交集恰好为 k + 1 k + 1 k+1的方案在交集至少为 k k k的方案里被算了 c k + 1 k c_{k + 1}^{k} ck+1k次的,因为从实际意义出发,交集为 k + 1 k + 1 k+1的情况相当于先选出了 k + 1 k+1 k+1个数,然后再从剩下的集合里选,先选出的这 k + 1 k + 1 k+1个数可以继续划分成为在交集为 k k k的时候选的是哪 k k k个,符合乘法原理。设 f ( i ) 表 示 交 集 元 素 至 少 为 i 个 的 方 案 数 f(i)表示交集元素至少为i个的方案数 f(i)表示交集元素至少为i个的方案数,那么 f ( k ) f(k) f(k) 应该减去 c k + 1 k f ( k + 1 ) c_{k + 1}^{k}f(k + 1) ck+1kf(k+1),这样就把 f ( k ) f(k) f(k)中交集恰好为 k + 1 k+1 k+1的都删掉了,但是对于恰好为 k + 2 k + 2 k+2的是什么样的呢? 本 来 f ( k ) − c k + 2 k × f ( k + 2 ) 可 以 把 f ( k ) 中 交 集 恰 好 为 k + 2 的 消 去 但 是 在 此 之 前 对 于 消 去 k + 1 的 操 作 会 影 响 其 中 k + 2 的 数 量 本来f(k) - c_{k+2}^{k}\times f(k + 2)可以把f(k)中交集恰好为k+2的消去但是在此之前对于消去k+1的操作会影响其中k+2的数量 本来f(k)−ck+2k×f(k+2)可以把f(k)中交集恰好为k+2的消去但是在此之前对于消去k+1的操作会影响其中k+2的数量
具体来看,f(k + 1)里面也有交集为k+2的集合,因此操作以后对于f(k)中交集为k + 2的相当于减了一个系数 c k + 2 k + 1 × c k + 1 k c_{k+2}^{k+1} \times c_{k+1}^{k} ck+2k+1×ck+1k 本来f(k)应该减一个 c k + 2 k c_{k+2}^{k} ck+2k 先在想让他变成原来的就要加一个 c k + 2 k c_{k+2}^{k} ck+2k
所以我们的容斥系数就是:
f[k] :1
f[k+1] :-C(k+1,k)
f[k+2] :-C(k+2,k)+C(k+1,k)*C(k+2,k+1) = +C(k+2,k)
f[k+3] :-C(k+3,k)+C(k+1,k)*C(k+3,k+1)-C(k+2,k)*C(k+3,k+2) = -C(k+3,k)
f[k+4] :-C(k+4,k)+C(k+1,k)*C(k+4,k+1)-C(k+2,k)*C(k+4,k+2)+C(k+3,k)*C(k+3,k+4) = +C(k+4,k)
最后得出来的式子就是
∑ i = k n ( − 1 ) i − k × c i k × f ( i ) \sum_{i=k}^{n}(-1)^{i-k}\times c_{i}^{k}\times f(i) ∑i=kn(−1)i−k×cik×f(i)
因为数很大很多,因此用快速幂带一个 l o g log log用快速幂求逆元的话会很慢,所以就用到了线性求逆元
线性求逆元:
假设要求i在模p意义下的逆元,设
p
=
k
×
i
+
r
p=k\times i + r
p=k×i+r
则有 k × i + r ≡ 0 ( m o d p ) k\times i + r \equiv 0 (mod p) k×i+r≡0(modp)
k × r − 1 + i − 1 ≡ 0 ( m o d p ) k\times r^{-1} + i^{-1} \equiv 0(mod p) k×r−1+i−1≡0(modp)
i − 1 ≡ − k × r − 1 ( m o d p ) i^{-1}\equiv-k\times r^{-1}(modp) i−1≡−k×r−1(modp)
k = ⌊ p i ⌋ k= \lfloor\frac{p}{i}\rfloor k=⌊ip⌋ r = p m o d i r = p\mod i r=pmodi
所以 i − 1 ≡ − ⌊ p i ⌋ × ( p m o d i ) − 1 ( m o d p ) i^{-1}\equiv-\lfloor\frac{p}{i}\rfloor\times (p\mod i)^{-1}(modp) i−1≡−⌊ip⌋×(pmodi)−1(modp)
infact[i] = - p / i * infact[p % i];
对于 2 2 n 2^{2^n} 22n这个来说我们可以倒着枚举,每次让他翻倍即可
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <set>
#include <queue>
#include <vector>
#include <map>
#include <unordered_map>
#include <cmath>
#include <stack>
#include <iomanip>
#include <deque>
#include <sstream>
#define x first
#define y second
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int f[N], fact[N], infact[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n >> k;
fact[0] = 1, infact[0] = infact[1] = 1;
for (int i = 1; i <= n; i ++ ) fact[i] = fact[i - 1] * (LL) i % mod;
for (int i = 2; i <= n; i ++ ) infact[i] = (LL)(mod - mod / i) * infact[mod % i] % mod;
for (int i = 2; i <= n; i ++ ) infact[i] = infact[i] * (LL) infact[i - 1] % mod;
int sum = 0, res = 0, t = 2;
for (int i = n; i >= k; i -- ) {
sum = (LL) fact[n] * infact[k] % mod * infact[n - i] % mod * infact[i - k] % mod * (t - 1) % mod;
t = (LL) t * t % mod;
if (i - k & 1) res -= sum;
else res += sum;
// res = (res + mod) % mod;
if (res >= mod) res -= mod;
if (res <= -mod) res += mod;
}
cout << (res + mod) % mod << endl;
return 0;
}