[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 }