题目描述
H 国有 \(n\)个城市,这 \(n\) 个城市用\(n−1\)条双向道路相互连通构成一棵树,\(1\)号城市是首都,也是树中的根节点。
H国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 HH 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
解题思路
1.处理倍增,因为根据贪心,越向上选择越好,这是显然的。我们用\(fa[i][j]\)表示从节点\(i\)向上走\(2^i\)步到达的节点,用\(dis[i][j]\)表示从节点\(i\)向上走\(2_i\)步所走过的距离。开始时DFS预处理一下即可。
2.二分答案,对于这道题,直接计算最小值显然比验证最小值♂太多了,我们发现这道题满足时间是有分界点的,大于等于答案的时间都满足,小于答案的都不满足。所以,我们可以二分答案。
3.处理每个点的军队向上能否走到根节点,假如军队走不到根节点,那这就简单了,管他咋走,走到最靠近根节点一定是最优的。加入走不到根节点,我们就要特殊处理了,我们用一个stack记录一下,用struct存储走到根节点后还能在规定时间内走多远(res),记录到根节点路上经过的根节点的直系儿子的编号(be)和一个vis表示有没有被处理过。我们从小到大按res排序,用DFS处理出来哪些根节点的直系儿子还需要检疫站,如果当前点不能回到来的时候经过的直系儿子并且直系儿子没有被选,并且直系儿子上要建检疫站,那就不要让它到根节点了,直接到直系儿子上去,struct的vis改成1,它不去,总有人得去,并且它去路径更短。
4.DFS处理要建检疫站的根节点的直系儿子,按根节点到儿子的距离排序,同样把stack按res排序,从小到大贪心选就好,如果剩下的直系儿子能选完,就代表可以。
5.记得判断一下有没有无解,不过这题也不卡这个,,
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn=500050;
int n,m,cnt=0,top=0,tot=0;
ll head[maxn<<1],ne[maxn<<1],to[maxn<<1],v[maxn<<1];
ll fa[maxn][20],dis[maxn][20],arm[maxn],bel[maxn],que[maxn];
bool vis[maxn],jam[maxn];
struct nod{
ll be,res;
bool vis;
};
nod stack[maxn];
inline bool cmp(nod x,nod y){
return x.res<y.res;
}
inline bool cmp1(int x,int y){
return dis[x][0]<dis[y][0];
}
inline void add(ll f,ll t,ll w){
ne[++cnt]=head[f],head[f]=cnt,to[cnt]=t,v[cnt]=w;
}
inline void dfs(int x,int f,ll w){
if(!bel[x])bel[x]=bel[f];
fa[x][0]=f,dis[x][0]=w;
for(register int i=1;i<=19;i++){
fa[x][i]=fa[fa[x][i-1]][i-1];
dis[x][i]=dis[x][i-1]+dis[fa[x][i-1]][i-1];
}
for(register int i=head[x];i;i=ne[i]){
if(to[i]==f)continue;
dfs(to[i],x,v[i]);
}
}
inline void find(int x,int f){
register int flag=0;
for(register int i=head[x];i;i=ne[i]){
flag++;
if(vis[to[i]]||to[i]==f)continue;
find(to[i],x);
}
if(flag==1){
if(!jam[bel[x]])jam[bel[x]]=1,que[++tot]=bel[x];
return;
}
}
inline void ini(){
top=0,tot=0;
memset(vis,0,sizeof(vis));
memset(jam,0,sizeof(jam));
}
inline bool check(ll x){
for(register int i=1,up=arm[i];i<=m;i++){
register ll tim=x;
for(register int j=19;j>=0;j--){
if(up==1||(!fa[up][j]))continue;
if(tim>=dis[up][j])tim-=dis[up][j],up=fa[up][j];
}
if(up==1){
nod tmp;
tmp.vis=0,tmp.be=bel[arm[i]],tmp.res=tim;
stack[++top]=tmp;
}
else {
vis[up]=1;
continue;
}
}
find(1,0);
sort(stack+1,stack+top+1,cmp);
for(register int i=1;i<=top;i++){
if(!jam[stack[i].be]||vis[stack[i].be])continue;
if(dis[stack[i].be][0]>stack[i].res)
stack[i].vis=1,vis[stack[i].be]=1;
}
tot=0;
memset(jam,0,sizeof(jam));
find(1,0);
sort(stack+1,stack+top+1,cmp);
sort(que+1,que+tot+1,cmp1);
register int match=1;
for(register int i=1;i<=top;i++){
if(stack[i].vis||match==tot+1)continue;
if(stack[i].res>=dis[que[match]][0])match++;
}
if(match==tot+1)return 1;
return 0;
}
int main(){
scanf("%d",&n);
for(register ll i=1,f,t,w;i<n;i++){
scanf("%lld%lld%lld",&f,&t,&w);
add(f,t,w),add(t,f,w);
}
int tt=0;
for(register int i=head[1];i;i=ne[i])bel[to[i]]=to[i],tt++;
dfs(1,0,0);
scanf("%d",&m);
for(register int i=1;i<=m;i++)scanf("%lld",&arm[i]);
if(tt>m){
cout<<-1<<endl;
return 0;
}
register int l=0,r=500000,ans=-1;
while(l<=r){
ini();
register int m=(l+r)>>1;
if(check(m))r=m-1,ans=m;
else l=m+1;
}
cout<<ans<<endl;
}