題目描述
一個有N個元素的集合有 2^N 個不同子集(包含空集),現在要在這2^N個集合中取出若干集合(至少一個),使得它們的交集的元素個數為K,求取法的方案數,答案模1000000007。(是質數喔~)
輸入格式
一行兩個整數N, K
輸出格式
一行為答案。
樣例
樣例輸入
3 2
樣例輸出
6
樣例說明
假設原集合為{A,B,C}
則滿足條件的方案為:{ {AB}, {ABC} }, { {AC}, {ABC} }, { {BC}, {ABC} }, {AB}, {AC}, {BC}
資料說明
對於100%的資料,1≤N≤1000000;0≤K≤N;
題解
對於這道題,根據樣例說明,很容易發現最後求的方案是“由集合構成的集合”,這就要求我們不能將單個元素看成一個數,而應該是由多個不同的數構成的集合(其中,不同的數的個數要大於等於k);
不妨設“不同的數的個數”為x;
但由於我們並不知道x的大小,只知道x的範圍(k~n),再看一眼資料範圍,可以列舉;
列舉過程中,相當於x已經確定,我們只需找這x個數構成的所有集合中,至少有k個數的集合與剩下沒有處理的數的全集的交集元素個數正好為k的情況;
- 對於 這x個數構成的所有集合中,至少有k個數的集合 這個東西,我們需要從n個數裡面選i個,再從i個數裡面選k個與後面去取交集;
公式為:
\[C(n, i) * C(i, k)
\]
- 對於 剩下沒有處理的數的全集的交集 這個東西,根據題目中一個有N個元素的集合有 2^N 個不同子集(包含空集)這個定理,很容易得出公式;
公式為:
\[2^{2^{n - i}} - 1
\]
有空集,所以-1;
但是可能會出現前面選了 {A, B}, 後面選{B, C},反過來後面選了 {A, B}, 前面選{B, C} 的情況,所以這時候要用容斥原理(不再解釋);
如果用Venn圖表示的話,最後整個的面積就是答案;
最後公式:
\[\sum^{i}_{k - n}C(n, i) * C(i, k) * (2^{2^{n - i}} - 1) * (-1)^{i - k}
\]
程式碼
#include <iostream>
using namespace std;
const long long mod = 1000000007;
long long n, k;
long long fc[1000005];
long long po[1000005];
long long qpow(long long a, long long b) {
long long ans = 1;
while(b) {
if (b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
inline long long inv(long long a, long long b) {
return qpow(a, b - 2);
}
inline long long C(long long n, long long m) {
return n < m ? 0 : fc[n] * inv(fc[m], mod) % mod * inv(fc[n - m], mod) % mod;
}
inline long long lucas(long long n, long long m) {
if (m == 0) return 1;
return C(n % mod, m % mod) * lucas(n / mod, m / mod) % mod;
}
int main() {
cin >> n >> k;
fc[0] = 1;
for (int i = 1; i <= n; i++) {
fc[i] = i * fc[i - 1] % mod;
}
po[0] = 2;
po[1] = 4;
for (int i = 2; i <= n; i++) {
po[i] = po[i - 1] * po[i - 1] % mod; //預處理出2^2^(n - i);
}
long long ans = 0;
for (int i = k; i <= n; i++) {
if ((i - k) % 2) {
ans = (ans % mod - lucas(i, k) * lucas(n, i) % mod * (po[n - i] - 1) % mod + mod) % mod;
} else {
ans = (ans % mod + lucas(i, k) * lucas(n, i) % mod * (po[n - i] - 1) % mod) % mod;
}
}
cout << ans;
return 0;
}