1. 程式人生 > >APIO 2012 守衛 | 差分 / 線段樹 + 貪心

APIO 2012 守衛 | 差分 / 線段樹 + 貪心

IT OS gpo sof 計算 span 區間 style 查找

題目:luogu 3634

首先把為 0 的區間刪去,重新標號,可以差分也可以線段樹。

把包含其他線段的線段刪去,原因 1 是它沒有用,原因 2 下面再說。然後,貪心選取最少的點來滿足所有線段,即選取還沒有點在上面的線段的右端點。如下圖中選取的紅色方格。

技術分享圖片

倘若不刪去包含其他線段的線段,如上圖中的藍色虛線,我們在貪心選取點的時候,就會先掃到藍線的左端點而後掃到第二條紅線,按照規則,我們會選擇藍線的右端點 6 號點,接下來掃到第二條紅線時,由於它上面並沒有點被選取,所以又會選取它的右端點 5 號點,這樣顯然不是最優的,這就是第 2 個原因。

選完點後,從左往右掃,每掃到一個選取的點,如果該點不是必選的(覆蓋它的線段 l ≠ r),就嘗試若不選它是否可行,即選取它左邊相鄰的點,計算出選取這個點時的最少點數,計算方法如下。

如上圖,用 F[ i ] 表示前 i 條線段需要選取的最少點數,G[ i ] 表示後 i 條線段需要選取的最少點數,假設現在我們嘗試不選 5 號點,計算選取 4 號點時需要選取的最少點數,然後與 k 比較,方法是二分找出右端點最大且小於 4 的線段 x,找出左端點最小且大於 4 的線段 y,若 F[ x ] + G[ y ] + 1 大於 k 則嘗試失敗,說明先前點是必選的。

 1 #include <cstdio>
 2 #include <string>
 3 #include <algorithm>
 4 
 5 const int
N = 100005; 6 7 struct line { 8 int l, r, f; 9 bool operator < (const line &cmp) const { 10 return l < cmp.l; 11 } 12 } a[N]; 13 14 int b[N], L[N], R[N], be[N], F[N], G[N]; 15 16 int read() { 17 int x = 0, f = 1; 18 char c = getchar(); 19 while
(!isdigit(c)) { 20 if (c == -) f = -1; 21 c = getchar(); 22 } 23 while (isdigit(c)) { 24 x = (x << 3) + (x << 1) + (c ^ 48); 25 c = getchar(); 26 } 27 return x * f; 28 } 29 30 int main() { 31 int n = read(), k = read(), m = read(); 32 for (int i = 1; i <= m; ++ i) { //差分標記為 0 的區間 33 a[i].l = read(), a[i].r = read(), a[i].f = read(); 34 if (a[i].f == 0) ++b[a[i].l], --b[a[i].r+1]; 35 } 36 int cur = 0, cnt = 0; 37 for (int i = 1; i <= n; ++ i) { //去除為 0 的區間並重新標號 38 cur += b[i]; 39 if (cur == 0) L[i] = R[i] = ++cnt, be[cnt] = i; 40 } 41 if (cnt == k) { //恰好滿足 42 for (int i = 1; i <= cnt; ++ i) printf("%d\n", be[i]); 43 return 0; 44 } 45 L[n + 1] = n + 1; 46 for (int i = 1; i <= n; ++ i) 47 if (R[i] == 0) R[i] = R[i - 1]; 48 for (int i = n; i >= 1; -- i) 49 if (L[i] == 0) L[i] = L[i + 1]; 50 cnt = 0; 51 for (int i = 1; i <= m; ++ i) { 52 if (a[i].f == 0) continue; 53 int l = L[a[i].l], r = R[a[i].r]; 54 if (l <= r) a[++cnt].l = l, a[cnt].r = r; 55 } 56 std::sort(a + 1, a + cnt + 1); 57 int top = 0; 58 for (int i = 1; i <= cnt; ++ i) { //去除包含其他線段的線段 59 while (top && a[i].l >= L[top] && a[i].r <= R[top]) --top; 60 L[++top] = a[i].l, R[top] = a[i].r; 61 } 62 int l = n + 1, r = 0; 63 for (int i = 1; i <= top; ++ i) { //貪心選取最少的點 64 if (L[i] > r) F[i] = F[i - 1] + 1, r = R[i]; 65 else F[i] = F[i - 1]; 66 } 67 for (int i = top; i >= 1; -- i) { 68 if (R[i] < l) G[i] = G[i + 1] + 1, l = L[i]; 69 else G[i] = G[i + 1]; 70 } 71 bool ok = 0; 72 for (int i = 1; i <= top; ++ i) { //嘗試不選 73 if (F[i] == F[i - 1]) continue; 74 if (L[i] == R[i]) { 75 printf("%d\n", be[R[i]]); 76 ok = 1; continue; 77 } 78 int l = 1, r = i - 1, x = 0, y = top + 1; 79 while (l <= r) { //二分查找 80 int mid = l + ((r - l) >> 1); 81 if (R[mid] < R[i] - 1) x = mid, l = mid + 1; 82 else r = mid - 1; 83 } 84 l = i + 1, r = top; 85 while (l <= r) { 86 int mid = l + ((r - l) >> 1); 87 if (L[mid] > R[i] - 1) y = mid, r = mid - 1; 88 else l = mid + 1; 89 } 90 if (F[x] + G[y] + 1 > k) { 91 printf("%d\n", be[R[i]]); 92 ok = 1; 93 } 94 } 95 if (!ok) puts("-1"); 96 return 0; 97 }

APIO 2012 守衛 | 差分 / 線段樹 + 貪心