LOJ 2567: 洛谷 P3643: bzoj 4584: 「APIO2016」劃艇
題目傳送門:LOJ #2249。
題意簡述:
有 \(n\) 個位置,第 \(i\) 個位置可以填在 \([a_i,b_i]\) (\(1\le a_i\le b_i\le 10^9\))之間的整數,也可以填 \(0\)。
如果第 \(i\) 個位置填了非 \(0\) 的數,則這個數必須大於之前所有位置(\(1\) 到 \(i-1\) 的位置)上的數。
至少要有一個位置填上非 \(0\) 的數。問最終有幾種填數方案,兩種填數方案不同當且僅當某個位置上填的數不同。
題解:
要求即為選出一些位置填數,並且形成嚴格遞增序列。
發現把所有區間離散化,可以組成 \(\mathcal{O}(n)\) 個連續段,每個連續段中的數的性質是相同的。
因為 \(n\) 不大(只有 \(500\)),考慮 DP。令 \(\mathrm{f}[i][j]\) 表示前 \(i\) 個位置,第 \(i\) 個位置填了在段 \(j\) 中的數的方案數。
顯然若 \([a_i,b_i]\) 不包含段 \(j\),則 \(\mathrm{f}[i][j]=0\)。對於其他的情況,考慮如何轉移,假設上一個填了在小於 \(j\) 的段中的數的位置是 \(k\),則有 \(\mathrm{f}[i][j]\) 從 \(\mathrm{f}[k][j']\) 轉移,其中 \(0\le k<i\) 且 \(1\le j'<j\)。
轉移的具體形式是:假設位置 \((k,i]\)
那麽我們有 \(\mathrm{f}[i][j]=\sum_{k=0}^{i-1}\binom{len+pos-1}{pos}\sum_{j'=0}^{j-1}\mathrm{f}[k][j']\)。邊界是 \(\mathrm{f}[0][0]=1\)、\(\mathrm{f}[0][j]=0\)(\(1\le j\))和 \(\mathrm{f}[i][0]=0\)(\(1\le i\))。
再利用前綴和優化,把第二維做前綴和,把轉移變成 \(\mathcal{O}(n)\) 的就好了,轉移內的組合數可以 \(\mathcal{O}(n)\) 預處理。
還可以滾動數組一下,空間就是 \(\mathcal{O}(n)\) 的了,不過沒必要。
#include <cstdio>
#include <algorithm>
typedef long long LL;
const int Mod = 1000000007;
const int MN = 505;
int N, lb[MN], rb[MN], lp[MN * 2], len[MN * 2], cnt;
int Inv[MN], C[MN], f[MN], Ans;
int main() {
scanf("%d", &N);
for (int i = 1; i <= N; ++i)
scanf("%d%d", &lb[i], &rb[i]),
lp[++cnt] = lb[i],
lp[++cnt] = rb[i] + 1;
std::sort(lp + 1, lp + cnt + 1);
cnt = std::unique(lp + 1, lp + cnt + 1) - lp - 2;
for (int i = 1; i <= cnt; ++i) len[i] = lp[i + 1] - lp[i];
for (int i = 1; i <= N; ++i)
lb[i] = std::lower_bound(lp + 1, lp + cnt + 1, lb[i]) - lp,
rb[i] = std::lower_bound(lp + 1, lp + cnt + 1, rb[i] + 1) - lp - 1;
Inv[1] = 1, C[0] = 1, f[0] = 1;
for (int i = 2; i <= N; ++i) Inv[i] = (LL)(Mod - Mod / i) * Inv[Mod % i] % Mod;
for (int i = 1; i <= cnt; ++i) {
int l = len[i];
for (int j = 1; j <= N; ++j)
C[j] = (LL)C[j - 1] * (l + j - 1) % Mod * Inv[j] % Mod;
for (int j = N; j >= 1; --j) if (lb[j] <= i && i <= rb[j]) {
for (int k = j, a = 0; k >= 1; --k) {
if (lb[k] <= i && i <= rb[k]) ++a;
f[j] = (f[j] + (LL)f[k - 1] * C[a]) % Mod;
}
}
}
for (int i = 1; i <= N; ++i) Ans = (Ans + f[i]) % Mod;
printf("%d\n", Ans);
return 0;
}
LOJ 2567: 洛谷 P3643: bzoj 4584: 「APIO2016」劃艇