P1973 [NOI2011]NOI 嘉年華
知識點: DP,單調性優化
原題面
參考: stO FlashHu 的題解 Orz
蒟蒻儘量把悟到的資訊都放了上來。
比較囉嗦,望見諒。
題意簡述:
給定 \(n\) 個事件,第 \(i\) 個事件從 \(S_i\)時刻開始,持續 \(T_i\) 時刻。
可將它們分到兩個地點中的一個,或者放棄。
同一地點事件可同時發生,兩個事件不可同時發生在不同地點。
求
- 無限制時,事件數較小的地點 的事件數。
- 不放棄第 \(i\) 個事件時,事件數較小的地點 的事件數。
\(1\le n \le 200, 0\le S_i\le10^9,1\le T_i\le 10^9\)。
分析題意
先離散化,下文中出現的 “時間” 均指離散化後的值。
顯然,離散化後時間的最大值 \(\le 2n\)
把事件看做線段,地點看做集合。
從 \(S_i\)時刻開始,持續 \(T_i\) 時刻的事件記為 \([s_i,t_i]\)。
有一個很顯然的結論:
對於一個分配方案,若存在某被丟棄的事件,可加入到集合中。
將其加入,答案不會變劣。
由上,若向某一集合中添加了一條線段 \([L,R]\),則所有包含於 \([L,R]\) 的線段,都一定會被新增到該集合中。
則可考慮將一個區間內的完整線段合併,
設 \(cnt_{i,j}\) 表示區間 \([i,j]\) 內的完整線段數。
暴力 \(O(n^3)\) 預處理即可。
先搞掉子任務一。
要求:事件數較小的地點 的事件數。
資料範圍不大,考慮列舉一邊的事件數,並求另一邊的最大值,兩邊取 \(\min\)
設 \(pre_{i,j}\) 表示,列舉到時間 \(i\),一邊線段數為 \(j\) 時,另一邊線段數的最大值。
轉移時列舉斷點 \(k\),則有:
\[pre_{i,j} = \max_{k=1}^{i}\{pre _{k,j} + cnt_{k,i},\ pre_{k,j-cnt_{k,i}}\}\]
兩種情況分別代表將 \([k,i]\) 新增到兩邊的情況。
轉移的總複雜度為 \(O(n^3)\)。
令 \(m\) 為時間的最大值,顯然答案為 \(\max\limits_{j=1}^{n}\{\min(pre_{m,j},j)\}\)。
來搞子任務二。
\(pre_{i,j}\)
再處理一個 \(suf_{i,j}\),表示 \(i\sim m\) 中,一邊線段數為 \(j\) 時,另一邊線段數的最大值。
轉移方程與 \(pre_{i,j}\) 類似,即為:
\[suf_{i,j} = \max_{k=i}^{m}\{suf_ {k,j} + cnt_{k,i},\ suf_{k,j-cnt_{k,i}}\}\]
此時屬於 \([s_i,t_i]\) 的線段,必包含在其中同一邊,但\(s_i\) 之前和 \(t_i\) 之後選擇的線段數 均未知。
同子任務一的思路,考慮列舉線段數,並求另一集合線段數的最大值,兩邊取 \(\min\) 即為所求。
設 \(f_{l,r}\) 為保證一邊必選 \([l,r]\) 中線段時,最優的答案。
列舉 \(x\) 為這邊中屬於 \([1,l-1]\) 的線段數, \(y\) 為 \([r+1,m]\) 中的線段數。
這一邊線段總數即為 \(x + cnt_{i,j} + y\)。
另一邊選擇的最大個數,顯然為 \(pre_{l,x} + suf_{r,y}\)。
則有狀態轉移方程:
\[f_{l,r} = \max_{x=1}^{n}\max_{y=1}^{n}\{\min(x + cnt_{i,j} + y,\ pre_{l,x} + suf_{r,y})\} \]
此時還有一種特殊情況。
當 \([s_i,t_i]\) 被一屬於同一集合的線段包含時,
上述 \(pre_{l,x} + suf_{r,y}\) 的計算方法就不合適了。
考慮列舉這樣的線段,以包含所有特殊情況。
顯然,第 \(i\) 個事件必選的答案即為:\(\max\limits_{l=1}^{s_i}\max\limits_{l=t_i}^{m}f_{l,r}\)。
這樣就必須計算出所有 \(f_{i,j}\)。
複雜度達到 \(O(n^4)\),會被卡掉。
程式碼實現
//知識點:DP
/*
By:Luckyblock
*/
#include <cstdio>
#include <ctype.h>
#include <algorithm>
#define max std::max
#define min std::min
#define ll long long
#define Calc(y) (min(x+cnt[l][r]+y,pre[l][x]+suf[r][y]))
const int kMaxn = 210;
const int kMaxm = 410;
const int kInf = 1e9 + 2077;
//=============================================================
struct Plan {
int S, T; //開始時間,結束時間
} p[kMaxn];
int data[kMaxm];
int n, max_time, ans, cnt[kMaxm][kMaxm];
int pre[kMaxm][kMaxn], suf[kMaxm][kMaxn]; //pre:1~i內一邊選j個,另一邊選擇的最大值。 suf:i~m
int f[kMaxm][kMaxm];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void Prepare() {
n = read();
for (int i = 1; i <= n; ++ i) {
data[++ max_time] = p[i].S = read();
data[++ max_time] = p[i].T = read() + p[i].S;
}
std :: sort(data + 1, data + max_time + 1);
max_time = std :: unique(data + 1, data + max_time + 1) - data - 1;
for (int i = 1; i <= n; ++ i) {
p[i].S = std :: lower_bound(data + 1, data + max_time + 1, p[i].S) - data;
p[i].T = std :: lower_bound(data + 1, data + max_time + 1, p[i].T) - data;
}
for (int i = 1; i <= n; ++ i) {
for (int l = 1; l <= p[i].S; ++ l) {
for (int r = p[i].T; r <= max_time; ++ r) {
cnt[l][r] ++;
}
}
}
}
//=============================================================
int main() {
Prepare();
for (int i = 1; i <= max_time; ++ i) {
for (int j = 1; j <= n; ++ j) {
pre[i][j] = suf[i][j] = - kInf;
}
}
for (int i = 1; i <= max_time; ++ i) {
for (int j = 0; j <= cnt[1][i]; ++ j) { //一邊的數量
for (int k = 1; k <= i; ++ k) { //列舉上一個時間點
pre[i][j] = max(pre[i][j], pre[k][j] + cnt[k][i]);
if (j >= cnt[k][i]) pre[i][j] = max(pre[i][j], pre[k][j - cnt[k][i]]);
}
}
}
for (int i = max_time; i >= 1; -- i) {
for (int j = 0; j <= cnt[i][max_time]; ++ j) { //一邊的數量
for (int k = i; k <= max_time; ++ k) { //列舉上一個時間點
suf[i][j] = max(suf[i][j], suf[k][j] + cnt[i][k]);
if (j >= cnt[i][k]) suf[i][j] = max(suf[i][j], suf[k][j - cnt[i][k]]);
}
}
}
for (int i = 1; i <= n; ++ i) ans = max(ans, min(pre[max_time][i], i));
printf("%d\n", ans);
for (int l = 1; l <= max_time; ++ l) {
for (int r = l + 1; r <= max_time; ++ r) {
for (int y = n, x = 0; x <= n; ++ x) {
int best = Calc(y), now;
while (y && best <= (now = Calc(y - 1))) best = now, -- y;
f[l][r] = max(f[l][r], Calc(y));
}
}
}
for (int i = 1; i <= n; ++ i) {
ans = 0;
for (int l = 1; l <= p[i].S; ++ l) {
for (int r = max_time; r >= p[i].T; -- r) {
ans = max(ans, f[l][r]);
}
}
printf("%d\n", ans);
}
return 0;
}