[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

  

猜你喜欢

转载自www.cnblogs.com/ww3113306/p/9928424.html