【二分+贪心+倍增】【NOIP2012】疫情控制
Description
H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。 H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。 现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。 请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
Input Format
第一行一个整数n,表示城市个数。 接下来的n-1行,每行3个整数,u、v、w,每两个整数之间用一个空格隔开,表示从城市u到城市v有一条长为w的道路。数据保证输入的是一棵树,且根节点编号为1。 接下来一行一个整数m,表示军队个数。 接下来一行m个整数,每两个整数之间用一个空格隔开,分别表示这m个军队所驻扎的城市的编号。
Output Format
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
Sample Input
4 1 2 1 1 3 2 3 4 3 2 2 2
Sample Output
3
Hint
保证军队不会驻扎在首都。
对于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。
Solution
因为军队同时进行,且边有权值,
每支军队走完一条边剩下的时间不同,
所以最少时间一定是最后一个军队的停下的时间。
很显然可以二分答案。
验证答案时,要用到贪心的思想,
首先对于每支军队,向上走能控制的点就越多,
所以每支军队都应该向上走,
不能走到根节点的军队,就驻扎在停下来的点,
并做上标记,最后计算没被控制的子树到达根节点的距离,
对于能走到根节点的点,记录每支军队的剩余时间,我们考虑是否走到其他子树,
把可以走到根节点的军队的剩余时间以及没被控制的子树到达根节点的距离从小到大排序,
然后贪心,剩余时间少的军队走距离根节点距离小的子树,
对于能走到根节点无法回到原子树的军队,如果原子树还未被控制,我们选择留在原子树,
因为如果选择其他子树,说明其他子树距离根节点的距离小于原子树到达根节点距离,
所以军队选择其他子树,并不比留在原子树优。
#include<cstdio> #include<cstring> #include<algorithm> int n,m; int num,head[50005],army[50005],f[50005][20]; long long dis[50005][20],l,r,mid,ans=0; bool se[50005]; struct node { int pos,dis; }A[50005],C[50005]; struct edge { int to,next,dis; }e[100005]; void add(int x,int y,int z) { e[++num].next=head[x]; e[num].to=y; e[num].dis=z; head[x]=num; } bool cmp(node a,node b) { return a.dis<b.dis; } void dfs(int x,int fa) { f[x][0]=fa; for (int i=1;i<17;i++) { f[x][i]=f[f[x][i-1]][i-1]; dis[x][i]=dis[x][i-1]+dis[f[x][i-1]][i-1]; } for (int i=head[x];i;i=e[i].next) { int v=e[i].to; if (v==fa) continue; dis[v][0]=e[i].dis; dfs(v,x); } } int up(int x,long long &dist) { for (int i=16;i>=0;i--) if (f[x][i]>1&&dist+dis[x][i]<=mid) dist+=dis[x][i],x=f[x][i]; return x; } bool go(int x,int fa) { if (se[x]) return 1; int ned=0; for (int i=head[x];i&&ned<3;i=e[i].next) ned++; if (ned==1&&e[head[x]].to==fa) return se[x]=0; for (int i=head[x];i;i=e[i].next) { if (e[i].to==fa) continue; if (!go(e[i].to,x)) return se[x]=0; } return se[x]=1; } bool check(int x) { memset(se,0,sizeof se); int nn=0,na=0; for (int i=1;i<=m;i++) { long long dist=0; int anc=up(army[i],dist); if (f[anc][0]==1&&(dist+dis[anc][0])<mid) A[++na].pos=anc,A[na].dis=mid-dist-dis[anc][0]; else se[anc]=true; } for (int i=head[1];i;i=e[i].next) { int v=e[i].to; if (!go(v,1)) C[++nn].pos=v,C[nn].dis=e[i].dis; } if (nn>na) return 0; std::sort(A+1,A+na+1,cmp),std::sort(C+1,C+nn+1,cmp); int j=1; for(int i=1;i<=na;i++) { if (!se[A[i].pos]) se[A[i].pos]=true; else if(C[j].dis<=A[i].dis) se[C[j].pos]=true; while (se[C[j].pos]&&j<=nn) j++; } return j>nn; } int main() { scanf("%d",&n); for (int i=1;i<n;i++) { int x,y,z; scanf("%d%d%d",&x,&y,&z); r+=z; add(x,y,z),add(y,x,z); } scanf("%d",&m); for (int i=1;i<=m;i++) scanf("%d",&army[i]); int notre=0; for (int i=head[1];i;i=e[i].next) notre++; if (notre>m) { printf("-1"); return 0; } dfs(1,0); while (l<=r) { mid=(l+r)>>1; if (check(mid)) { ans=mid; r=mid-1; } else l=mid+1; } printf("%lld",ans); }