1. 程式人生 > 實用技巧 >P1973 [NOI2011]NOI 嘉年華

P1973 [NOI2011]NOI 嘉年華

知識點: DP,單調性優化

原題面

參考: stO FlashHu 的題解 Orz

蒟蒻儘量把悟到的資訊都放了上來。
比較囉嗦,望見諒。

題意簡述:

給定 \(n\) 個事件,第 \(i\) 個事件從 \(S_i\)時刻開始,持續 \(T_i\) 時刻。
可將它們分到兩個地點中的一個,或者放棄。
同一地點事件可同時發生,兩個事件不可同時發生在不同地點。

  1. 無限制時,事件數較小的地點 的事件數。
  2. 不放棄第 \(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}\)

,即為 \(1\sim 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;
}