疫情控制

疫情控制

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

也是树中的根节点。

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

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

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

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

现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。

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

输入
第一行一个整数 n,表示城市个数。

接下来的 n-1 行,每行 3 个整数,u、v、w,每两个整数之间用一个空格隔开,表示从

城市 u 到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。

接下来一行一个整数 m,表示军队个数。

接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎

的城市的编号。

输出
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。

样例输入 [复制]
4
1 2 1
1 3 2
3 4 3
2
2 2
样例输出 [复制]
3

我们要明确我们做什么,一步一步慢慢来,否则会被这题逼疯。

1.预处理倍增
我们会发现,离根节点越近的节点,控制的节点更多。所以由贪心的思想,所有的军队都要尽可能地往根节点走。

那么我们可以dfs一遍,将倍增要用的一些值都处理

2.二分答案
军队可以同时移动,说明我们要控制传染病的时间是军队移动到位时,移动时间最长的军队的移动时间。而我们要求最小值,即要求最大化最小值。

二分答案一般用于求最大化最小值,最小化最大值。——jyf神犇

jyf的博客

所以就是二分啦,二分一个答案,事情就会更有方向。

3.”上提“军队
使用倍增的方法将军队在二分出的答案限制内尽力往上”提“,不过不可以到根节点。

4.处理剩余路程
如果当前军队可以到达根节点,那么记录一下它的编号和它到达根节点后还可以走的时间rest。如果这个军队i在根节点的子树x中,那么记录一下子树x的符合这个条件的点中,到根节点后剩余路程最短的点。

如果不可以到达,记录它被”提“到的节点被军队设置了检查点。

5.dfs找未被”封死“的子树
如果一个节点建立了检查点或者它的所有子树都设立了检查点,则说明以这个节点为根的子树已经被“封死”。记录根节点的所有子树中,未被“封死”的子树。

6.军队在子树间转移
将我们已经记录好了的可以到根节点的军队按照剩余路程从大到小排序。

将未被“封死”的子树按照到子树到根节点的距离从大到小排序。

然后依次处理未被“封死”的子树要由哪支军队来管辖。

当然离根节点远的军队由剩余路程大的军队来管辖是吼滴啦,不过缀吼滴还是就由本来就在这棵子树上的军队来管辖。所以我们先查看我们事先记录的(在子树x中,可以到达根节点,且到根节点后剩余路程最小的军队)是否被使用,如果被使用,再看当前没有被使用的军队里剩余路程最大的可否到达这棵子树。

这样我们就可以判断当前二分出的答案是否可行了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define LL long long
int read(){
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
    return q;
}
const int N=50005;
int n,m,tot,na,nb;
int h[N],ne[N<<1],to[N<<1];LL w[N<<1];
int f[N][18],army[N];LL dis[N][18];
void add(int x,int y,int z)
{to[++tot]=y,ne[tot]=h[x],h[x]=tot,w[tot]=z;}
void dfs(int x,int las,LL havego){//1.预处理倍增
    f[x][0]=las,dis[x][0]=havego;
    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=h[x];i!=-1;i=ne[i])
        if(to[i]!=las)dfs(to[i],x,w[i]);
}
struct node{LL rest;int id;}a[N],b[N];
int vis[N],used[N],restbj[N];LL restmin[N];
int checkok(int x,int las){//5.dfs找未被“封死”的子树
    int bj=1,i,bbj=0;
    if(vis[x])return 1;
    for(i=h[x];i!=-1;i=ne[i]){
        if(to[i]==las)continue;bbj=1;
        if(!checkok(to[i],x)){
            bj=0;
            if(x==1) b[++nb].id=to[i],b[nb].rest=w[i];
            else return 0;
        }
    }
    if(!bbj)return 0;
    return bj;
}
bool cmp(node x,node y){return x.rest>y.rest;}
int check(LL lim){
    int i,j,x,now;LL num;na=nb=0;
    for(i=1;i<=n;++i)vis[i]=restbj[i]=0;
    for(i=1;i<=m;++i)used[i]=0;
    for(i=1;i<=m;++i){
        x=army[i],num=0;
        for(j=17;j>=0;--j)//3.上提军队
            if(f[x][j]>1&&num+dis[x][j]<=lim)
            num+=dis[x][j],x=f[x][j];
        if(f[x][0]==1&&num+dis[x][0]<=lim){//4.处理剩余路程
            a[++na].rest=lim-num-dis[x][0],a[na].id=i;
            if(!restbj[x]||a[na].rest<restmin[x])
                restmin[x]=a[na].rest,restbj[x]=i;
        }
        else vis[x]=1;
    }
    if(checkok(1,0))return 1;
    sort(a+1,a+1+na,cmp),sort(b+1,b+1+nb,cmp);//6.军队在子树间转移
    now=1;used[0]=1;
    for(i=1;i<=nb;++i){
        if(!used[restbj[b[i].id]]){used[restbj[b[i].id]]=1;continue;}
        while(now<=na&&(used[a[now].id]||a[now].rest<b[i].rest))++now;
        if(now>na)return 0;used[a[now].id]=1;
    }
    return 1;
}
int main()
{
    int i,x,y,z;LL l=0,r=500000,mid,ans=-1;
    n=read();
    for(i=1;i<=n;++i)h[i]=-1;
    for(i=1;i<n;++i){
        x=read(),y=read(),z=read();
        add(x,y,z),add(y,x,z);
    }
    dfs(1,0,0);m=read();
    for(i=1;i<=m;++i)army[i]=read();
    while(l<=r){//2.二分答案
        mid=(l+r)>>1;
        if(check(mid))r=mid-1,ans=mid;
        else l=mid+1;
    }
    printf("%lld",ans);
    return 0;
}
  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值