zoukankan      html  css  js  c++  java
  • [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
    "); 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
    ", 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

      

  • 相关阅读:
    linux下安装EJBCA 搭建私有CA服务器
    PHP 设计模式之观察者模式
    PHP 设计模式之三种工厂模式
    PHP 设计模式之单例模式
    解決 VMware Workstation 与 Device/Credential Guard 不相容,无法启动虚拟机的问题
    Mac 外接鼠标不好用?这个软件解决你的痛点
    PHP Trait 解决 PHP 单继承问题
    Wordpress 添加图片点击放大效果
    PHP 实现 WebSocket 协议
    Web 网页直接打开 Windows 软件
  • 原文地址:https://www.cnblogs.com/ww3113306/p/9928424.html
Copyright © 2011-2022 走看看