P3643 [APIO2016] 划艇

小超手123發表於2024-03-15

題意:

在首爾城中,漢江橫貫東西。在漢江的北岸,從西向東星星點點地分佈著 \(N\) 個划艇學校,編號依次為 \(1\)\(N\)。每個學校都擁有若干艘划艇。同一所學校的所有划艇顏色相同,不同的學校的划艇顏色互不相同。顏色相同的划艇被認為是一樣的。每個學校可以選擇派出一些划艇參加節日的慶典,也可以選擇不派出任何划艇參加。如果編號為 \(i\) 的學校選擇派出划艇參加慶典,那麼,派出的划艇數量可以在 \(a_i\)\(b_i\) 之間任意選擇(\(a_i \leq b_i\))。

值得注意的是,編號為 \(i\) 的學校如果選擇派出划艇參加慶典,那麼它派出的划艇數量必須大於任意一所編號小於它的學校派出的划艇數量。

輸入所有學校的 \(a_i,b_i\) 的值,求出參加慶典的划艇有多少種可能的情況,必須有至少一艘划艇參加慶典。兩種情況不同當且僅當有參加慶典的某種顏色的划艇數量不同。

\(N \le 500,a_{i},b_{i} \le 10^9\)

分析:

由於值域有 \(10^9\),因此需要離散化成一個個小段。

發現 dp 的難點在於每個學校可以不用派出划艇。

不如利用將每個學校分成連續的一段,來刻畫這個過程,在同一段的學校派出的潛艇顏色為同一段(也可以不派出),並欽定每一段的最後一個學校必選

因此記 \(f_{i,j}\) 表示考慮前 \(i\) 個學校,並且第 \(i\) 個學校選顏色段數編號為 \(j\)

轉移可以列舉這一段的開始的學校 \(l\),和上一段的結尾顏色段數編號 \(k\)

\[f_{i,j}=\sum_{l=1}^{i} \tbinom{m+L-1}{m} \sum_{k=1}^{j-1}f_{l-1,k} \]

其中 \(\tbinom{m+L-1}{m}\) 表示將 \(m\) 個物品放進 \(L\) 個位置,每個物品可以不放在任何一個位置,且不能為空的方案數量。考慮構造

\[0\ 0\ 0...0 \ 1 \ 2...L(共 \ m-1 \ 個 \ 0) \]

選第 \(i\)\(0\) 表示第 \(i\) 個物品不放,選 \(x\ (x \ne 0)\) 表示第 \(x\) 個不選 \(0\) 的物品放在 \(x\) 這個位置上。

而轉移中 \(\sum_{k=1}^{j-1}f_{l-1,k}\) 可以用字首和最佳化。

因此時間複雜度 \(O(n^3)\)

程式碼:

#include<bits/stdc++.h>
#define int long long
#define N 3010
#define mod 1000000007
using namespace std;
int Pow(int a, int n) {
	if(n == 0) return 1;
	if(n == 1) return a;
	int x = Pow(a, n / 2);
	if(n % 2 == 0) return x * x % mod;
	else return x * x % mod * a % mod;
}
int inv(int x) {
	return Pow(x, mod - 2);
}
int n, ans;
struct node {
	int l, r;
}a[N];
int b[N], tot, c[N], len, L[N], R[N];
int h[N][N], fac[N], Inv[N], f[N][N], g[N][N];
int C(int j, int m) {
	return h[j][m] * Inv[m] % mod;
}
signed main() {
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i].l >> a[i].r;
		b[++tot] = a[i].l;
		b[++tot] = a[i].r;
	}
	sort(b + 1, b + tot + 1);
	for(int i = 1; i <= tot; i++) 
		if(i == 1 || b[i] != b[i - 1]) c[++len] = b[i];
	int H = 0;
	for(int i = 1; i < len; i++) {
		H++;
		L[H] = c[i];
		R[H] = c[i];
		if(c[i] + 1 <= c[i + 1] - 1) {
			H++;
			L[H] = c[i] + 1;
			R[H] = c[i + 1] - 1;
		}
	}
	H++;
	L[H] = c[len];
	R[H] = c[len];
	len = H;
	for(int i = 1; i <= len; i++) {
		h[i][0] = 1;
		int x = R[i] - L[i] + 1;
		for(int j = 1; j <= n; j++) h[i][j] = h[i][j - 1] * (x + j - 1) % mod;
	}
	fac[0] = Inv[0] = 1;
	for(int i = 1; i <= n; i++) fac[i] = fac[i - 1] * i % mod;
	Inv[n] = inv(fac[n]);
	for(int i = n - 1; i >= 1; i--) Inv[i] = Inv[i + 1] * (i + 1) % mod;
	f[0][0] = 1;
	for(int i = 0; i <= len; i++) g[0][i] = 1;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= len; j++) { //列舉選什麼
			g[i][j] = g[i][j - 1];
			if(a[i].l <= L[j] && R[j] <= a[i].r) ;
			else continue;
			int m = 0;
			for(int ll = i; ll >= 1; ll--) {
				if(a[ll].l <= L[j] && R[j] <= a[ll].r) m++;
				f[i][j] += g[ll - 1][j - 1] * C(j, m) % mod;
				f[i][j] %= mod;
			}
			ans = (ans + f[i][j]) % mod;
			g[i][j] = (g[i][j] + f[i][j]) % mod;
		}
	}
	cout << ans;
	return 0;
}