codevs1218 疫情控制

疫情控制
(blockade.cpp/c/pas)
【问题描述】
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,也是树中的根节点。
H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
【输入】
输入文件名为 blockade.in。
第一行一个整数 n,表示城市个数。
接下来的 n-1 行,每行 3 个整数,u、v、w,每两个整数之间用一个空格隔开,表示从城市 u 到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。
接下来一行一个整数 m,表示军队个数。
接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎的城市的编号。
【输出】
输出文件为 blockade.out。
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
【输入输出样例说明】
第一支军队在 2 号点设立检查点,第二支军队从 2 号点移动到 3 号点设立检查点,所需时间为 3 个小时。
【数据范围】
保证军队不会驻扎在首都。
对于 20%的数据,2≤ n≤ 10;
对于 40%的数据,2 ≤n≤50,0<w <10 5 ;
对于 60%的数据,2 ≤ n≤1000,0<w <10 6 ;
对于 80%的数据,2 ≤ n≤10,000;
对于 100%的数据,2≤m≤n≤50,000,0<w <10 9 。

正解:贪心+二分答案+倍增

解题报告:

  写每年NOIP的T3找感觉。。。

  这道题开始觉得很码农,打完之后发现也就那样。。。网上题解讲的并不是很清楚,我详细讲一讲吧。

  这题还是运用一些比较神奇的东西。题意求一个最大值的最小值,我居然第一眼没看出来二分答案,显然直接二分一个时间,然后判断是否可行。

  考虑如何判断可行,得到一个时间之后,我们就可以知道所有军队是否能到达1号(首都),也就是根结点,为了很快求出来我们需要预处理一下距离,倍增自然最好。我们不妨对于每一支军队都算一下是否可以到达根结点,不能的话就记录一下最多能跳到哪里。对于这道题,我们可以很明显地看出越往上越优,所以我们对于不能到达根的就尽可能地往上跳,到他能达到的最高结点,打上标记,表示这个结点可以到达。然后我们就dfs一遍,自下往上判断一下,如果一个结点的所有儿子都已经存在封锁到叶子结点的距离了,那么这个结点也意味着封锁了,往上update就可以了。

  接下来我们统计一下根结点的所有儿子结点,那些已经封锁边境的我们肯定不需要再管了,已经可以覆盖了。我们只要考虑尚未封锁的,显然对于每个儿子结点,若想封闭这个儿子结点到边境,最优的肯定是直接封锁这个儿子结点。所以我们可以考虑对于那些可以到达根结点的军队,把他们派往各个未被封锁的儿子结点。当然,如果可派出的军队数如果还没有未封锁的儿子结点数量多,那么无论如何不可行。

  我们现在就有一些军队到达根结点之后剩余的行动时间和每个未被封锁的儿子结点到根结点的距离。我们需要将他们进行匹配,也就是把军队派往各地。首先,我们必须明确,如果某只军队可以到达根结点,而他所在的根结点的儿子结点的那棵树中,不妨设为root,肯定把他直接派往root驻扎即可,因为把他派往别的未被封锁的城市的话,还需要另外调军队过来驻扎这个root,肯定不会更优,具体证明略。

  综上,我们的判断做法就是:军队按剩余时间排序,未被封锁的儿子结点按到根结点距离排序,而每支军队如果他的root未被封锁,则直接派往root,否则选择一个距离最近的未被封锁的儿子结点,前往驻扎,因为如果最近的都无法派往的话,这支军队就不能发挥任何作用了。(这是一个经典的O(N)匹配。。。)

  就这么做啦,代码如下:

  1 //It is made by jump~
  2 #include <iostream>
  3 #include <cstdlib>
  4 #include <cstring>
  5 #include <cstdio>
  6 #include <cmath>
  7 #include <algorithm>
  8 #include <ctime>
  9 #include <vector>
 10 #include <queue>
 11 #include <map>
 12 #include <set>
 13 using namespace std;
 14 typedef long long LL;
 15 const int MAXN = 50011;
 16 LL inf;
 17 int n,m,ecnt,a[MAXN];
 18 int root[MAXN];//属于根结点的哪个子结点
 19 int next[MAXN*2],to[MAXN*2],first[MAXN],w[MAXN*2];
 20 int f[MAXN][17];
 21 LL g[MAXN][17];
 22 LL l,r,ans,mid;
 23 bool pd[MAXN];
 24 struct node{
 25     LL remain;
 26     int root;
 27 }b[MAXN];
 28 struct son{
 29     LL dis;
 30     int pos;
 31 }c[MAXN];
 32 
 33 inline int getint()
 34 {
 35        int w=0,q=0; char c=getchar();
 36        while((c<'0' || c>'9') && c!='-') c=getchar(); if(c=='-') q=1,c=getchar(); 
 37        while (c>='0' && c<='9') w=w*10+c-'0', c=getchar(); return q ? -w : w;
 38 }
 39 
 40 inline bool cmp(node q,node qq){ return q.remain<qq.remain;  }
 41 inline bool ccmp(son q,son qq){ return q.dis<qq.dis; }
 42 
 43 inline void dfs(int x,int fa){
 44     for(int i=first[x];i;i=next[i]) {
 45     int v=to[i]; if(v==fa) continue;
 46     if(x==1) root[v]=v; else root[v]=root[x];
 47     dfs(v,x); f[v][0]=x; g[v][0]=w[i];
 48     }
 49 }
 50 
 51 inline void dfs2(int x,int fa){
 52     bool flag=false,ff=true;
 53     for(int i=first[x];i;i=next[i]) {
 54     int v=to[i]; if(v==fa) continue;
 55     flag=true; dfs2(v,x);
 56     if(!pd[v]) ff=false;
 57     }
 58     if(ff && flag && x!=1) pd[x]=1;
 59 }
 60 
 61 inline bool check(LL limit){
 62     int u; LL total;
 63     int cnt=0; memset(pd,0,sizeof(pd));
 64     for(int i=1;i<=m;i++) {
 65     u=a[i]; total=limit;
 66     for(int j=16;j>=0;j--) {
 67         if(total>=g[u][j] && f[u][j]!=0) {
 68         total-=g[u][j];
 69         u=f[u][j];
 70         }
 71     }
 72     if(u==1) {
 73         b[++cnt].remain=total;
 74         b[cnt].root=root[a[i]];
 75     }
 76     else pd[u]=1;
 77     } 
 78 
 79     dfs2(1,0);//处理跳到一半到达不了根的情况,往上update,注意顺序!!!
 80 
 81     ecnt=0;//统计根结点的未被覆盖的儿子个数
 82     for(int i=first[1];i;i=next[i]) {
 83     if(pd[to[i]]) continue;//统计未被覆盖的!!!
 84     c[++ecnt].pos=to[i];
 85     c[ecnt].dis=w[i];
 86     }
 87 
 88     if(ecnt>cnt) return false;
 89     sort(b+1,b+cnt+1,cmp); sort(c+1,c+ecnt+1,ccmp);   
 90     int now=1;//当前处理到的根结点的儿子结点
 91     c[ecnt+1].dis=inf;
 92     for(int i=1;i<=cnt;i++) {
 93     if(!pd[b[i].root]) pd[b[i].root]=1; //当前军队处在这个儿子结点尚未被覆盖,那么直接派遣他过去驻扎就可以了
 94     else {
 95         while(pd[c[now].pos]) now++;
 96         if(b[i].remain>=c[now].dis) pd[c[now].pos]=1,now++;
 97     }
 98     while(pd[c[now].pos]) now++;
 99     }
100     if(now>ecnt) return true;
101     return false;
102 }
103 
104 inline void work(){
105     n=getint(); int x,y,z;
106     for(int i=1;i<n;i++) {
107     x=getint(); y=getint(); z=getint();
108     next[++ecnt]=first[x]; first[x]=ecnt; to[ecnt]=y; w[ecnt]=z;
109     next[++ecnt]=first[y]; first[y]=ecnt; to[ecnt]=x; w[ecnt]=z;
110     }
111     m=getint(); for(int i=1;i<=m;i++) a[i]=getint();
112     dfs(1,0);
113     for(int j=1;j<=16;j++)//注意倍增细节,更新顺序!!!
114     for(int i=1;i<=n;i++){
115         f[i][j]=f[f[i][j-1]][j-1];
116         g[i][j]=g[f[i][j-1]][j-1]+g[i][j-1];
117     }
118     inf=1; for(int i=1;i<=40;i++) inf*=2;
119     l=0; r=inf;
120     if(!check(r)) { printf("-1"); return ; }
121     while(l<=r) {
122     mid=(l+r)/2;
123     if(check(mid)) ans=mid,r=mid-1;
124     else l=mid+1;
125     }
126     printf("%lld",ans);
127 }
128 
129 int main()
130 {
131   work();
132   return 0;
133 }
原文地址:https://www.cnblogs.com/ljh2000-jump/p/5837504.html