[NOIP] [LCA] [贪心] NOIP2012Day2 疫情控制(blockade)

4 篇文章 0 订阅
3 篇文章 0 订阅

//题解真心毒瘤,阅读时请做好心理准备

题目传送门

因为被审查禁了,重写一下题解……
显然知道这是一棵树,根据贪心的思路,我们尽可能将军队向根节点移动,但是不能移动到根节点。
怎么跑?
要往上走,就得处理这个点以上所有直系父节点
这里写图片描述
//eg:这棵树中, 5 5 5的所有直系父节点就是 2 2 2 1 1 1 6 6 6的所有直系父节点就是 3 , 1 3,1 3,1

跑的时候每一个有军队的节点向靠近根节点的方向走,直到不能走为止。如果一个军队没有走到根节点,那么它显然只能去看守最后到达的那个节点,这已经是最优方案。接下来我们考虑对于到达根节点的所有点中,我们记录每个点的两个属性:一是他来自哪里,二是他还能走多长时间的路。接下来我们把剩余到达根节点的点按照剩余路程从小到大排序,记为数组 A A A,把根节点的叶子节点中没有被覆盖的节点取出,并且按照到达根节点的距离进行从小到大排序,记为数组 B B B。最后我们把两个数组进行归并,用 A A A中小的去覆盖 B B B中小的。
贪心部分完成
好像还是不明白怎么解…
求最小时间可以二分答案,如果B完全覆盖,则时间可行,否则时间不可行
不错
怎么实现?
处理直系父节点:DFS找出第二层的儿子们,把他们的第二层都记为这个点。
处理 1 1 1号节点到各个节点的距离和第二层节点的子节点到第二层节点的距离
各位没吐吧…
上代码

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 50010
using namespace std;

struct link
{
    int to;
    long long val;
    int nxt;
};

int n,m;
int u,v;
long long w;
int one_son_num;
long long sum_val;
int army[MAXN];
//Input&Perwork

link e[2*MAXN];
int edge_num[MAXN],cnt;
//Link_Table

int father[MAXN];
long long dis[MAXN];
int second_floor[MAXN];
long long second_to_first[MAXN];
long long leaf_to_second[MAXN];
//DFS

bool vis[MAXN];
long long rest[MAXN];
int cur[MAXN],pre[MAXN];
int match[MAXN];
bool can[MAXN];
//check

bool cmp(int a,int b)
{
    return dis[a]<dis[b];
}

bool cmp1(int a,int b)
{
    return second_to_first[a]>second_to_first[b];
}

bool cmp2(int a,int b)
{
    return a>b;
}

void add(int u, int v, long long w)
{
    e[cnt]=(link){v,w,edge_num[u]}; edge_num[u]=cnt++;
    e[cnt]=(link){u,w,edge_num[v]}; edge_num[v]=cnt++;
}

void dfs1(int node,int nowfather,long long len) //处理每个点到1号节点的距离
{
    father[node]=nowfather;
    dis[node]=len;
    for (int i=edge_num[node];~i;i=e[i].nxt)
        if (e[i].to!=nowfather)
            dfs1(e[i].to,node,len+e[i].val);
}

void dfs2(int node,bool have_branch,int bigfather,long long len)//处理第二层的子节点到第二层的距离,如果只有一个节点,则不处理
{
    second_floor[node]=bigfather;
    if (!have_branch)
    {
        int cntson=0;
        for (int i=edge_num[node];~i;i=e[i].nxt)
            if (e[i].to!=father[node])
            {
                cntson++;
                if (cntson==2)
                {
                    have_branch=true;
                    break;
                }
            }
    }
    leaf_to_second[node]=len;
    for (int i=edge_num[node];~i;i=e[i].nxt)
        if (e[i].to!=father[node])
            if (have_branch) dfs2(e[i].to,have_branch,bigfather,len+e[i].val);
            else dfs2(e[i].to,have_branch,bigfather,0);
}

bool check(long long time)
{
    memset(vis,false,sizeof(vis));//vis表示该点已有军队
    for (int i=edge_num[1];~i;i=e[i].nxt)
    {
        can[e[i].to]=false;
        cur[e[i].to]=0;
    }
    rest[0]=0;
    for (int i=1;i<=m;i++)
    {
        if (time>dis[army[i]]) //足够走到1号节点,可以走到其他第二层
        {
            rest[++rest[0]]=time-dis[army[i]];
            pre[rest[0]]=cur[second_floor[army[i]]];//链状维护
            cur[second_floor[army[i]]]=rest[0];
        }
        else //只能走到自己的第二层
        {
            if (time>=leaf_to_second[army[i]])
                can[second_floor[army[i]]]=true;
        }
    }
    match[0]=0;
    for (int i=edge_num[1];~i;i=e[i].nxt)
        if (!can[e[i].to]) match[++match[0]]=e[i].to;
    if (match[0]>rest[0]) return false;//剩下可去其他第二层节点的军队不够需要军队的第二层节点
    sort(match+1,match+match[0]+1,cmp1);//按与1号节点距离从大到小排序
    sort(rest+1,rest+rest[0]+1,cmp2);//按剩余时间从大到小排序
    for (int i=1,j=1;i<=match[0]&&j<=rest[0];i++)//覆盖
    {
        while (vis[cur[match[i]]]) cur[match[i]]=pre[cur[match[i]]];//找未覆盖的军队
        if (cur[match[i]]) vis[cur[match[i]]]=true;
        else
        {
            if (rest[j]<second_to_first[match[i]]) return false;//过不去
            vis[j]=true;
        }
        while (vis[j]) j++;
    }
    return true;
}

long long binary()
{
    long long left=0,right=sum_val;
    while (left+1!=right)
    {
        long long middle=(left+right)>>1;
        if (check(middle)) right=middle;
        else left=middle;
    }
    return right;
}

int main()
{
    memset(edge_num,-1,sizeof(edge_num));
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        scanf("%d %d %lld",&u,&v,&w);
        add(u,v,w);sum_val+=w;
    }
    dfs1(1,-1,0);
    for (int i=edge_num[1];~i;i=e[i].nxt)
    {
        one_son_num++;
        second_to_first[e[i].to]=e[i].val;
        dfs2(e[i].to,false,e[i].to,0);
    }
    scanf("%d",&m);
    if (one_son_num>m)
    {
        printf("-1\n");
        return 0;
    }
    for (int i=1;i<=m;i++) scanf("%d",&army[i]);
    sort(army+1,army+m+1,cmp);
    printf("%lld\n",binary());
    return 0;
}

写了三天,一脸懵逼…

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值