NOIP2012 洛谷P1084 疫情控制

题目:

H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,

也是树中的根节点。

H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境

城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境

城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,

首都是不能建立检查点的。

现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在

一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等

于道路的长度(单位:小时)。

请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

 

参考链接:https://www.cnblogs.com/linda-fcj/p/7217198.html

 

分析:二分答案,然后对于每支军队,到根节点的子节点的距离小于mid的全都到根节点的子节点集中,其他军队都尽量往上走,能走到哪就是哪(还要注意,如果一个节点的所有子节点都有军队,那么这个节点也就相当于有军队)。然后将根节点的子节点上的军队剩余时间排序,剩余时间最小且无法做到去一趟根节点再回来的军队驻守在原来的子节点上,其他军队都到根节点上,然后排序,并将所有没有军队的子节点排序,贪心扫描一遍即可(最后判断一下根节点的子节点是不是都有军队,如果都有,就输出时间,否则输出-1)

 

代码:

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 #define maxn 50010
  6 using namespace std;
  7 struct node  //定义边信息的结构体
  8 {
  9     int to;
 10     int next;
 11     int w;
 12 }edge[maxn*2];
 13 struct shudui
 14 {
 15     int gra;  //这个gra是根节点下的一个子节点
 16     int v;    //这个代表距离
 17 }extra[maxn],defic[maxn];
 18 int head[maxn*2],arm[maxn],fath[maxn],gran[maxn],dis[maxn],cal[maxn],tim[maxn],b[maxn];
 19 int n,m,tot=0,cnt=0,sum=0,na=0,nc=0;
 20 bool cmp(const shudui &a,const shudui &b)  //定义排序方式
 21 {
 22     return a.v>b.v;
 23 }
 24 void add_edge(int u,int v,int ww) //添加边信息
 25 {
 26     cnt++;
 27     edge[cnt].to=v;
 28     edge[cnt].next=head[u];
 29     edge[cnt].w=ww;
 30     head[u]=cnt;
 31 }
 32 
 33 void Init(int x)
 34 {
 35     for(int i=head[x];i;i=edge[i].next)
 36     {
 37         int vv=edge[i].to;
 38         if(fath[x]!=vv)
 39         {
 40             if(x==1)
 41                 gran[vv]=vv; //gran里面保存的是x这个节点所在树的这一支上的根节点的子节点
 42             else
 43                 gran[vv]=gran[x];
 44             fath[vv]=x;
 45             dis[vv]=dis[x]+edge[i].w;
 46             Init(vv);
 47         }
 48     }
 49 }
 50 
 51 void dfs(int root)
 52 {
 53     int maxx=-1,now1=0,now2=0;
 54     for(int i=head[root];i;i=edge[i].next)
 55     {
 56         int vv=edge[i].to;
 57         if(fath[root]!=vv)
 58         {
 59             dfs(vv);  //如果tim等于-1,就代表在这个点上要不然就是没有军队驻扎,要不然就是军队都到达了根节点
 60             if(tim[vv]==-1) now2=1;//该点尚未被覆盖
 61             if(tim[vv]>=edge[i].w)  now1=1;//该点可以被覆盖
 62             maxx=max(maxx,tim[vv]-edge[i].w);
 63         }
 64     }
 65     if(root!=1&&edge[head[root]].next)  //这个root不是根,且不是叶子节点
 66     {
 67         if(now1) tim[root]=max(tim[root],maxx);
 68         else if(now2) tim[root]=max(tim[root],-1);
 69         else tim[root]=max(tim[root],0);  //这个判断就代表如果root的所有子节点的那颗子树都有军队,那就把父节点标记成也有军队驻扎
 70     }
 71 }
 72 
 73 void cal_tim(int ti)
 74 {
 75     for(int i=1;i<=m;i++)
 76         if(dis[arm[i]]>=ti) tim[arm[i]]=ti;  //tim表示的是这个点最多可以移动到顶点的距离是多少
 77     dfs(1);                         //给tim赋值就表示在这里驻扎的军队不能往树的上一节进发
 78 }
 79 
 80 void cal_extra(int ti)
 81 {
 82     for(int i=1;i<=m;i++)
 83     {
 84         if(dis[arm[i]]<ti)
 85         {
 86             extra[++na].gra=gran[arm[i]];
 87             extra[na].v=ti-dis[arm[i]];//extra表示的是根节点可以向下移动的距离
 88         }
 89     }
 90     sort(extra+1,extra+na+1,cmp);
 91     for(int i=1;i<=na;i++)
 92     {
 93         if(!cal[extra[i].gra]) cal[extra[i].gra]=i;  //下面else的判断就是如果这个节点已经被覆盖过了,但是之前
 94         else if(extra[cal[extra[i].gra]].v>extra[i].v) cal[extra[i].gra]=i;//被覆盖的那个节点更适合用于其他地方,那就换掉它
 95     }
 96 }
 97 
 98 void cal_defic()
 99 {
100     for(int i=head[1];i;i=edge[i].next)
101     {
102         int vv=edge[i].to;  //记录一下根节点下的哪一个节点还没有被覆盖
103         if(tim[vv]==-1)
104         {
105             defic[++nc].gra=vv;
106             defic[nc].v=edge[i].w;   //这个表示的是这个未被覆盖的节点到达根节点的距离
107         }
108     }
109     sort(defic+1,defic+nc+1,cmp);
110 }
111 /*
112 这个函数作用就是看可不可以通过extra去覆盖defic结构体里面不满足题意的点
113 */
114 bool remedy(int x)
115 {
116 //    if(x==0)
117 //    {
118 //        printf("%d %d\n",na,nc);
119 //    }
120     if(na<nc) return false;
121     memset(b,0,sizeof(b));
122     int i=1,j=1;
123     for(;i<=nc;i++)
124     {
125         if(!b[cal[defic[i].gra]] && cal[defic[i].gra])
126             b[cal[defic[i].gra]]=1;    //如果这个点在cal_extra函数的时候就已经被分配好了,那就还按原本的
127         else  //否则的话这个点之前没有被其他点去覆盖,那就遍历找一个最佳点去覆盖它
128         {
129             while(j<=na && b[j]) j++;
130             if(j>na || extra[j].v<defic[i].v)  return false;
131             b[j]=1,j++;
132         }
133     }
134     return true;
135 }
136 
137 bool Check(int ti)  //检查这个时间是不是可以满足题意
138 {
139     memset(cal,0,sizeof(cal));
140     na=nc=0;
141     memset(tim,-1,sizeof(tim));
142     cal_tim(ti);
143     cal_extra(ti);
144     cal_defic();
145     return remedy(ti);
146 }
147 
148 int erfen()  //二分枚举题目要求最少时间
149 {
150     int l=-1,r=999999999;
151     while(l+1<r)
152     {
153         int mid=(l+r)/2;
154         if(Check(mid))  r=mid;
155         else l=mid;
156     }
157     return r;
158 }
159 int main()
160 {
161     cin>>n;
162     memset(head,0,sizeof(head));
163     for(int i=1;i<n;i++)
164     {
165         int x,y,ww;
166         cin>>x>>y>>ww;
167         add_edge(x,y,ww);
168         add_edge(y,x,ww);
169         if(x==1 || y==1)  //tot就是根节点下子节点的数量
170             tot++;
171     }
172     cin>>m;
173     if(tot>m)  //它的意思是如果军队数量少于这个,那不管时间多大都不会完全覆盖
174     {
175         cout<<-1;
176         return 0;
177     }
178     Init(1);
179     for(int i=1;i<=m;i++)
180         cin>>arm[i];
181     sort(arm+1,arm+1+m);
182     cout<<erfen();
183     return 0;
184 }
185 /*
186 函数解释:
187 
188 void add_edge(int,int,int);//建图
189 void dfs(int);//处理出要被覆盖的点和已被覆盖的点
190 void cal_tim(int);//处理出每个军队的能力,能到,不能到
191 void cal_extra(int);//能到的还有多少剩余
192 void cal_defic();//不能到的还差多少
193 bool remedy(); //能到的能不能补齐不能到的
194 bool Check(int);//二分的时间是否够(其实就是调用以上四个函数)
195 void Init(int);//处理出各个城市所属的直辖市,根节点的儿子
196 int erfen();//二分时间
197 
198 示例:
199 7
200 1 2 1
201 1 3 1
202 2 4 1
203 2 5 1
204 3 6 1
205 3 7 1
206 4
207 4 5 6 7
208 
209 输出:
210 0
211 */

 

posted @ 2020-06-06 16:44  kongbursi  阅读(204)  评论(0编辑  收藏  举报