Description
H国有n个城市,这n个城市用n-1条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。
H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
Input
第一行一个整数n,表示城市个数。
接下来的n-1行,每行3个整数,u、v、w,每两个整数之间用一个空格隔开,表示从城市u到城市v有一条长为w的道路。数据保证输入的是一棵树,且根节点编号为1。
接下来一行一个整数m,表示军队个数。
接下来一行m个整数,每两个整数之间用一个空格隔开,分别表示这m个军队所驻扎的城市的编号。
Output
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-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。
A了这道题之后无意中发现可以把自己的代码hack掉(放出的代码是已经修改完的,啊当然有可能还会被hack掉)
以下是数据:2 1 2 5 1 2,应输出0。
这组数据也可以hack掉网上的很多题解……然而vijos和codevs都没卡。
方法是二分+贪心+拓扑排序/倍增,这里用的是拓扑排序。
贪心可得,每个军队尽量要往上走。二分出mid之后,走不到根节点的军队就走到他们所能到达的最靠近根节点的节点,能走到根节点的军队则扔进一个队列里,再将根节点尚未被控制的子节点扔进另一个队列,每个子节点需要一支军队控制。贪心可得,将军队走到根节点后的剩余时间从小到大sort一发,再按根节点到子节点所需时间将子节点sort一发,一一匹配即可。
详见代码。
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const int N=;
int n,m,cnt,u,v,w,cnt1,cnt2,first[N];
int army[N],q[N],fa[N],anc[N],t1[N],t2[N];
//anc[i]代表控制节点i的根节点的子节点的编号
bool notl[N],arr[N],mark[N];
//arr[i]代表节点i的子树所包括的叶子结点是否全部被控制
ll l,r,tot,dis[N],cost[N],t[N];
//t[i]代表军队往上走到达节点i剩余时间最大值
struct edge{int next,to,w;}e[N*];
int read()
{
int x=,f=;char c=getchar();
while(c<''||c>''){if(c=='-')f=-;c=getchar();}
while(c>=''&&c<=''){x=x*+c-'';c=getchar();}
return x*f;
}
void ins(int u,int v,int w)
{
cnt++;e[cnt].to=v;e[cnt].w=w;
e[cnt].next=first[u];first[u]=cnt;
}
bool cmp1(int a,int b){return cost[a]<cost[b];}
bool cmp2(int a,int b){return dis[army[a]]>dis[army[b]];}
void bfs()
{
int h=,t=;q[++t]=;
while(h<=t)
{
int u=q[h++];
notl[u]=false;
for(int i=first[u];i;i=e[i].next)
{
int v=e[i].to;
if(v==fa[u])continue;
dis[v]=dis[u]+e[i].w;
fa[v]=u;cost[v]=e[i].w;//cost[i]代表节点i到其父节点所需时间
if(u!=)anc[v]=anc[u];
else anc[v]=v;
q[++t]=v;
notl[u]=true;
}
}
}
bool check(ll mid)
{
memset(t,-,sizeof(t));
for(int i=;i<=n;i++)arr[i]=notl[i];
for(int i=n;i;i--)//按bfs的顺序倒着处理
{
int u=q[i];
if(mark[u]&&dis[u]>mid)t[u]=mid;
if(t[fa[u]]<t[u]-cost[u])t[fa[u]]=t[u]-cost[u];
if(t[u]>=)arr[u]=true;
if(!arr[u])arr[fa[u]]=false;
//若军队能到达节点u或节点u的子树所包括的叶子结点已全部被控制,arr[u]=true
}
cnt1=cnt2=;
for(int i=first[];i;i=e[i].next)
if(!arr[e[i].to])t1[++cnt1]=e[i].to;//将根节点尚未被控制的子节点扔进队列
for(int i=;i<=m;i++)
if(dis[army[i]]<=mid)t2[++cnt2]=i;//将所有可以使用的军队扔进队列
if(cnt1==)return true;//若已经全部控制,则直接返回true
sort(t1+,t1+cnt1+,cmp1);
sort(t2+,t2+cnt2+,cmp2);
for(int i=,j=;i<=cnt2;i++)
{
if(!arr[anc[army[t2[i]]]])arr[anc[army[t2[i]]]]=true;
else if(cost[t1[j]]+dis[army[t2[i]]]<=mid)arr[t1[j]]=true;
while(j<=cnt1&&arr[t1[j]])j++;
if(j>cnt1)return true;
}
return false;
}
int main()
{
n=read();
for(int i=;i<n;i++)
{
u=read();v=read();w=read();
r+=w;ins(u,v,w);ins(v,u,w);
}
tot=r;
m=read();
for(int i=;i<=m;i++)
army[i]=read(),mark[army[i]]=true;
bfs();
while(l<=r)
{
ll mid=(l+r)/;
if(check(mid))r=mid-;
else l=mid+;
}
if(l==tot+)printf("-1");
else printf("%lld",l);
return ;
}