Bzoj2839:集合计数
题意:
一个有nnn个元素的集合有2n2^n2n个不同子集(包含空集),现在要在这2n2^n2n个集合中取出若干集合(至少一个),使得它们的交集的元素个数为kkk,求取法的方案数,答案模1000000007。
题解:
可以先枚举选取哪k个元素,一共有cnkc_{n}^{k}cnk种情况,选取kkk个之后还剩下n−kn-kn−k个数字,对于剩下的数字每个可以选或不选一共有2n−k2^{n-k}2n−k种情况,即这么多种集合,对于每一个集合又可以选或不选,那么一共有22n−k2^{2^{n-k}}22n−k种选法,但是必须要有一个集合附带上前面选取的kkk个元素才可以,因此要减去空集那种情况,所以最终的方案数就是cnk×(22n−k−1)c_{n}^{k}\times(2^{2^{n-k}} - 1)cnk×(22n−k−1) ,但是我们发现,当前并不是交集恰好为kkk的情况,剩下的集合中是可能会有相同元素的,这个式子的实际含义是交集至少为kkk的方案数。
怎么求呢?首先设g(i)表示交集元素恰好是i的方案数g(i)表示交集元素恰好是i的方案数g(i)表示交集元素恰好是i的方案数,我们只需要让方案数至少是k的减去大于k的恰好的方案数即可,也就是cnk×(22n−k−1)−∑i=k+1ng(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(n2)O(n^2)O(n2)会T,是可以优化的。
就用到了容斥原理,我们发现对于交集恰好为k+1k + 1k+1的方案在交集至少为kkk的方案里被算了ck+1kc_{k + 1}^{k}ck+1k次的,因为从实际意义出发,交集为k+1k + 1k+1的情况相当于先选出了k+1k+1k+1个数,然后再从剩下的集合里选,先选出的这k+1k + 1k+1个数可以继续划分成为在交集为kkk的时候选的是哪kkk个,符合乘法原理。设f(i)表示交集元素至少为i个的方案数f(i)表示交集元素至少为i个的方案数f(i)表示交集元素至少为i个的方案数,那么f(k)f(k)f(k) 应该减去ck+1kf(k+1)c_{k + 1}^{k}f(k + 1)ck+1kf(k+1),这样就把f(k)f(k)f(k)中交集恰好为k+1k+1k+1的都删掉了,但是对于恰好为k+2k + 2k+2的是什么样的呢?本来f(k)−ck+2k×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的相当于减了一个系数ck+2k+1×ck+1kc_{k+2}^{k+1} \times c_{k+1}^{k}ck+2k+1×ck+1k 本来f(k)应该减一个ck+2kc_{k+2}^{k}ck+2k 先在想让他变成原来的就要加一个ck+2kc_{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=kn(−1)i−k×cik×f(i)\sum_{i=k}^{n}(-1)^{i-k}\times c_{i}^{k}\times f(i)∑i=kn(−1)i−k×cik×f(i)
因为数很大很多,因此用快速幂带一个logloglog用快速幂求逆元的话会很慢,所以就用到了线性求逆元
线性求逆元:
假设要求i在模p意义下的逆元,设p=k×i+rp=k\times i + rp=k×i+r
则有k×i+r≡0(modp)k\times i + r \equiv 0 (mod p)k×i+r≡0(modp)
k×r−1+i−1≡0(modp)k\times r^{-1} + i^{-1} \equiv 0(mod p)k×r−1+i−1≡0(modp)
i−1≡−k×r−1(modp)i^{-1}\equiv-k\times r^{-1}(modp)i−1≡−k×r−1(modp)
k=⌊pi⌋k= \lfloor\frac{p}{i}\rfloork=⌊ip⌋ r=pmod ir = p\mod ir=pmodi
所以 i−1≡−⌊pi⌋×(pmod i)−1(modp)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];
对于22n2^{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;
}
本文解析了BZOJ 2839题目的解法,利用组合数学和容斥原理计算在给定条件下选取集合的方案数,重点在于理解交集数量限制下的选择策略,并通过线性求逆元优化计算。
1582

被折叠的 条评论
为什么被折叠?



