[Lucas定理] 集合計數

Peppa_Even_Pig發表於2024-04-15

題目描述

一個有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的情況;

  1. 對於 這x個數構成的所有集合中,至少有k個數的集合 這個東西,我們需要從n個數裡面選i個,再從i個數裡面選k個與後面去取交集;

公式為:

\[C(n, i) * C(i, k) \]

  1. 對於 剩下沒有處理的數的全集的交集 這個東西,根據題目中一個有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;
}

相關文章