洛谷 P1084 疫情控制

本文介绍了一个算法问题,通过在树形结构的城市网络中合理部署军队来阻止疫情从首都蔓延到边境城市。利用贪心策略和图论知识,文章提供了一种高效求解方案。

题目描述
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,也是树中的根节点。H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。


【题目分析】
贪心。


【代码】

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
int n,m;
int army[50001];
int h[100001],to[100001],ne[100001],w[100001],en=0;
int f[50001][17],top[50001],have[50001],need[50001],all;
long long needtime[50001];int minpos[50001];
long long mintop[50001];
long long g[50001][17],l,r,dis[50001];
long long last[50001];
void add(int a,int b,int c)
{to[en]=b;ne[en]=h[a];w[en]=c;h[a]=en++;}
void dfs(int k,int tp)
{
    top[k]=tp;
    for (int i=h[k];i>=0;i=ne[i])
    {
        if (to[i]!=f[k][0])
        {
            f[to[i]][0]=k;
            g[to[i]][0]=w[i];
            dis[to[i]]=dis[k]+w[i];
            if (k==1) dfs(to[i],to[i]);
            else dfs(to[i],tp);
        }
    }
}
bool dfs(int k)
{
    bool ret=true;
    if (have[k])
        return true;
    if (k==1)
        for (int i=h[k];i>=0;i=ne[i])
        {
            if (!dfs(to[i])) need[++all]=to[i],needtime[all]=w[i];
        }
    else
    {
        int flag=0;
        for (int i=h[k];i>=0;i=ne[i])
            if (to[i]!=f[k][0])
            {
                flag=1;
                if (!dfs(to[i])) ret=false;
            }
        if (flag==0) return false;
    }
    return ret;
}
bool cmp1(long long a,long long b){return a>b;}
bool ok(long long mid)
{
    int cnt=0;all=0;
    memset(have,0,sizeof have);
    memset(mintop,-1,sizeof mintop);
    memset(minpos,0,sizeof minpos);
    for (int i=1;i<=m;++i)
    {
        if (dis[army[i]]<=mid)
        {
            last[++cnt]=mid-dis[army[i]];
            if (last[cnt]<mintop[top[army[i]]]||mintop[top[army[i]]]==-1)
            {
                if (mintop[top[army[i]]]!=-1)
                {
                    mintop[top[army[i]]]=min(mintop[top[army[i]]],last[cnt]);
                    minpos[top[army[i]]]=cnt;
                }
                else
                {
                    mintop[top[army[i]]]=last[cnt];
                    minpos[top[army[i]]]=cnt;
                }
            }
        }
        else
        {
            int now=army[i];
            long long tmp=mid;
            for (int j=16;j>=0;j--)
            {
                if (tmp>=g[now][j])
                {
                    tmp-=g[now][j];
                    now=f[now][j];
                }
            }
            have[now]=1;
        }
    }
    dfs(1);
    for (int i=1;i<=all;++i)
    {
        if (mintop[need[i]]!=-1&&last[minpos[need[i]]]<needtime[i])
        {
            last[minpos[need[i]]]=-1,needtime[i]=-1;
        }
    }
    sort(needtime+1,needtime+all+1,cmp1); while (needtime[all]<=0&&all) all--;
    sort(last+1,last+cnt+1,cmp1); while (last[cnt]<=0&&cnt) cnt--;
    if (cnt<all) return false;
    for (int i=1;i<=all;++i)
        if (last[i]<needtime[i])
            return false;
    return true;
}
int main()
{
    memset(h,-1,sizeof h);
    scanf("%d",&n);
    for (int i=1;i<n;++i)
    {
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
        add(b,a,c);
    }
    scanf("%d",&m);
    for (int i=1;i<=m;++i) scanf("%d",&army[i]);
    dfs(1,0);
    for (int j=1;j<=16;++j)
        for (int i=1;i<=n;++i)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            g[i][j]=g[i][j-1]+g[f[i][j-1]][j-1];
        }
    l=0,r=1e10;
    long long ans;
    while (l<=r)
    {
        long long mid=(l+r)/2;
        int tmp=ok(mid);
        if (tmp>0)
        {
            ans=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    printf("%lld\n",ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值