Bzoj2839:集合计数

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 nk个数字,对于剩下的数字每个可以选或不选一共有 2 n − k 2^{n-k} 2nk种情况,即这么多种集合,对于每一个集合又可以选或不选,那么一共有 2 2 n − k 2^{2^{n-k}} 22nk种选法,但是必须要有一个集合附带上前面选取的 k k k个元素才可以,因此要减去空集那种情况,所以最终的方案数就是 c n k × ( 2 2 n − k − 1 ) c_{n}^{k}\times(2^{2^{n-k}} - 1) cnk×(22nk1) ,但是我们发现,当前并不是交集恰好为 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×(22nk1)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+2k+1k+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)ik×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+r0(modp)

k × r − 1 + i − 1 ≡ 0 ( m o d p ) k\times r^{-1} + i^{-1} \equiv 0(mod p) k×r1+i10(modp)

i − 1 ≡ − k × r − 1 ( m o d p ) i^{-1}\equiv-k\times r^{-1}(modp) i1k×r1(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) i1ip×(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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值