1. 程式人生 > >[NOIP2012]疫情控制 貪心 二分

[NOIP2012]疫情控制 貪心 二分

題面:[NOIP2012]疫情控制

題解:

  大體思路很好想,但是有個細節很難想QAQ

  首先要求最大時間最小,這種一般都是二分,於是我們二分一個時間,得到一個log。

  然後發現一個軍隊,越往上走肯定可以控制的葉節點越多,因此我們在時間範圍內儘量向上走,又得到一個log了。

  如果一個軍隊走到根後還有多餘時間,那它就有可能走到根的其他兒子去幫助其他子樹。

  然後為了儘可能覆蓋多的子樹,顯然應該要用剩餘時間少的軍隊,對應走過去代價小的子樹,所以sort一下就可以了?

  然而還有一種情況,那就是一個點從它的子樹出發到了root,萬一最後需要回到它自己那個子樹,直接做就把代價算了2次,這樣就可能導致本來可以不花代價就回到原來的子樹的,但我們卻花了雙倍代價。。。。於是可能就把一個合法的時間判成不合法了。

  所以應該怎麼做?

  其實只需要在 每個子樹中可以走出去幫助其他子樹的軍隊 裡面選一個剩餘時間最少的,走出去不能回來的軍隊守護自己就可以了(前提是自己還需要守護)。

  可以證明,這樣肯定是最優的。

  因為如果把所有軍隊都提上去的話,意味這你要用軍隊x來幫助其他子樹,同時意味著要從其他子樹選一個軍隊y來幫助它。那麼觀察到既然軍隊x出來後無法回去,卻可以幫助某個子樹u,因此到這個被幫助的子樹u的代價要比回去的代價小。所以如果我們用軍隊x來守護自己所在的子樹,那麼原本從其他子樹中選出來幫助它的軍隊y就可以去守護子樹u,因為子樹u代價比當前子樹小,因此子樹u一定可以被軍隊y守護到。

  所以肯定不會變劣。

  寫的時候還有一些細節,,,

  1 #include<bits/stdc++.h>
  2 using namespace std;
  3 #define R register int
  4 #define AC 51000
  5 #define ac 101000
  6 #define LL long long
  7 
  8 int n, m, num, cnt, rnt;
  9 LL all;
 10 int Head[AC], Next[ac], date[ac], tot;
 11 int p[AC], father[AC][18
]; 12 LL st[AC][18], have[AC], len[ac]; 13 bool z[AC], used[AC], vis[AC]; 14 15 struct node{ 16 int x; 17 LL rest; 18 friend bool operator < (node a, node b){ 19 return a.rest < b.rest; 20 } 21 }s[AC], son[AC]; 22 23 inline int read() 24 { 25 int x = 0;char c = getchar(); 26 while(c > '9' || c < '0') c = getchar(); 27 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 28 return x; 29 } 30 31 inline void add(int f, int w, int S) 32 { 33 date[++ tot] = w, Next[tot] = Head[f], Head[f] = tot, len[tot] = S; 34 date[++ tot] = f, Next[tot] = Head[w], Head[w] = tot, len[tot] = S; 35 } 36 37 void pre() 38 { 39 n = read(); 40 for(R i = 1; i < n; i ++) 41 { 42 int a = read(), b = read(), c = read(); 43 add(a, b, c), all += c; 44 } 45 father[1][0] = 1;//父親設為自己,防止子樹裡面的點跳到0 46 m = read(); 47 for(R i = 1; i <= m; i ++) p[i] = read(); 48 } 49 50 void dfs1(int x)//預處理倍增陣列 get: st father have son 51 { 52 vis[x] = true; 53 for(R i = 1; i <= 17; i ++) 54 { 55 father[x][i] = father[father[x][i - 1]][i - 1]; 56 st[x][i] = st[x][i - 1] + st[father[x][i - 1]][i - 1]; 57 } 58 int now; 59 for(R i = Head[x]; i; i = Next[i]) 60 { 61 now = date[i]; 62 if(vis[now]) continue; 63 if(x == 1) son[++ num] = (node){now, len[i]}; 64 father[now][0] = x, st[now][0] = len[i]; 65 have[now] = have[x] + len[i], dfs1(now);//記錄下從root到now的距離 66 } 67 } 68 69 void dfs2(int x)//找到哪些節點還沒有被控制 70 { 71 if(z[x]) return ; 72 int now;bool done = true, flag = false; 73 for(R i = Head[x]; i; i = Next[i]) 74 { 75 now = date[i]; 76 if(now == father[x][0]) continue; 77 dfs2(now), flag = true; 78 if(!z[now]) done = false;//如果兒子裡面有一個不合法的,這個節點就不合法 79 }//不能直接return,因為1號節點一般都不合法,但其他兒子還要標記的,,, 80 if(flag) z[x] = done;//否則所有兒子都合法,那這個點就合法,但是如果這個點是葉子,,,就不能平白無故打標記了 81 } 82 83 bool check(int mid)//判斷這個時間是否合法 84 { 85 cnt = rnt = 0; 86 memset(z, 0, sizeof(z)); 87 for(R i = 1; i <= m; i ++) 88 { 89 int x = p[i], now = 0; 90 for(R j = 17; j >= 0; j --) 91 if(father[x][j] != 1 && now + st[x][j] <= mid) 92 now += st[x][j], x = father[x][j];//記得要先加後跳 93 if(have[x] >= mid - now) z[x] = true;//無法到達別的子樹 94 else s[++ cnt] = (node){x, mid - now};//可以到達 95 } 96 dfs2(1); 97 //sort(s + 1, s + cnt + 1); 98 for(R i = 1; i <= cnt; i ++)//分配一個不能回來的給當前子樹 99 if(s[i].rest < 2 * have[s[i].x] && !z[s[i].x]) z[s[i].x] = true; 100 else s[++ rnt] = s[i], s[rnt].rest -= have[s[i].x];//提到root的同時要加上去root的代價 101 sort(s + 1, s + rnt + 1);//排序。 102 int l = 1; 103 for(R i = 1; i <= num; i ++)//剩下的從小到大依次匹配 104 { 105 if(z[son[i].x]) continue; 106 while(s[l].rest < son[i].rest && l <= rnt) ++ l; 107 if(l > rnt) return false; 108 ++ l;//把這個用了 109 } 110 return true; 111 } 112 113 void half()//二分時間 114 { 115 if(num > m) {printf("-1\n"); return ;}//如果root兒子數大於軍隊數,那麼永遠不可能全部覆蓋 116 sort(son + 1, son + num + 1); 117 int l = 0, r = all, mid; 118 while(l < r) 119 { 120 mid = (l + r) >> 1; 121 if(check(mid)) r = mid; 122 else l = mid + 1; 123 } 124 printf("%d\n", l); 125 } 126 127 int main() 128 { 129 freopen("in.in", "r", stdin); 130 pre(); 131 dfs1(1); 132 half(); 133 fclose(stdin); 134 return 0; 135 }
View Code