noip 模拟赛 Po姐姐与他的妹子A

A(A.cpp)

Time Limit:1s Memory Limit:128MB

【题目背景】

Po姐姐很爱他的妹子,可是Po姐姐并没有妹子。于是Po姐姐决定去找妹子。

【题目描述】

A国有n个城镇,由n-1条道路连接,构成了一个树形结构。每条道路的长度都是一样的。

Po姐姐最近得到了一个信息:在A国的某些城镇,可能出现质量上乘的妹子。

为了捕获这些妹子,Po姐姐制作了m个传送器,准备将这些传送器安置在一些城镇中。一旦某个城镇出现了妹子,Po姐姐可以立刻传送到某个传送器所在的城镇,然后沿道路移动到妹子所在的城镇。Po姐姐并不愿意走太多的路,因此他会选择离妹子最近的一个传送器传送,然后走最短路到达妹子所在的城镇。

Po姐姐想要通过合理安排传送器的位置,使得所有妹子可能出现的城镇离最近传送器的距离的最大值最小。

由于Po姐姐懒癌发作,请你帮他写一个程序来解决这个问题。Po姐姐懒得写SPJ,你只需要输出最大距离的最小值就行了。

【数据输入】

第一行是两个正整数n,m,表示A国城镇的数量和传送器的数量

接下来n个整数,每个整数都是1或0,如果第i个整数是1代表第i个城镇可能出现质量上乘的妹子

接下来n-1行,每行两个正整数x,y,表示x与y之间有一条双向通行的道路

【数据输出】

输出一行一个正整数,代表所有妹子可能出现的城镇离最近传送器的最大距离的最小值

【样例输入】

7 2

1 0 1 1 0 1 1

1 3

2 3

3 4

4 5

5 6

5 7

【样例输出】

1

【样例解释】

如图所示,在城镇3和城镇5上放置传送器,这样城镇1、4、6、7离最近传送器的距离为1,城镇3离最近传送器的距离为0,故最大值为1。

【数据范围】

对于10%的数据,1<=m<=n<=10

对于40%的数据,1<=m<=n<=1000

对于100%的数据,1<=m<=n<=3*10^5

 

这题有难度啊

首先二分答案是很好想的,但是很久以前就看过一句话:“二分答案的考点永远是在检验上。”

所以...怎么检验呢?

在检验时,原题可以转化为“已知每个点最大覆盖的距离,求至少几个点能覆盖所有特殊点?”

那么我们就可以用一个树形dp+贪心的思想来检验。

设我们要检验的答案为x:

贪心:只有当一个节点离子树内最远的无人覆盖的特殊点距离恰好为x时,我们才在这个节点上放置一个传送器(当然,根节点除外)。

证明:设这个点节点离无人覆盖的特殊点距离小于x,那么我们完全可以在这个节点的父节点上放置一个传送器来覆盖,而且这样还可以覆盖更多的点,何乐而不为?

在dp时,我们先搜索后回溯更新:

设sta[i]表示i节点的状态(只有1,2两个值,1代表这个点的子树中有特殊点未被覆盖,离这个节点的距离为f[i],2代表这个点的子树中有传送器,传送器还能覆盖的最大范围为f[i])(也就是说,由这个点状态的不同,f数组的含义不同!这点请千万注意,我一开始就是不知道f数组的含义才没看懂题解...)

那么对于每个点,我们枚举他的所有子节点,在回溯时判断:


if(sta[to]==1)//表示这个点子树中有节点还未被覆盖但需覆盖 
{
    temp1=max(temp1,f[to]+1);//需要覆盖的距离就会变大 
}else if(sta[to]==2)//表示这个点子树中没有节点需要被覆盖而且可以向上覆盖 
{
    temp2=max(temp2,f[to]-1);//找出最多能向上覆盖多少(最多能向上覆盖的距离就会变小)
}

等判断完,调整这个节点的状态即可:

if(temp1>temp2)//能覆盖别人的点覆盖不到需要被覆盖的点 
    {
        if(temp1==val)//距离恰好是二分的答案 
        {
            tot++;//增加一个 
            f[x]=val;//能覆盖的距离增大 
            sta[x]=2;//状态为2 
        }else
        {
            sta[x]=1;//未被覆盖但不必新加一个节点 
            f[x]=temp1;//记录下最大距离 
        }
    }else
    {
        sta[x]=2;//能够被覆盖 
        f[x]=temp2;//记下最大能向上覆盖多远 
    }

贴整体代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
struct Edge
{
    int next;
    int to;
}edge[600005];
int head[300005];
int cnt=1;
void init()
{
    memset(head,-1,sizeof(head));
    cnt=1;
}
void add(int l,int r)
{
    edge[cnt].next=head[l];
    edge[cnt].to=r;
    head[l]=cnt++;
}
int n,m;
int tru[300005];
int f[300005];//记录... 
int sta[300005];//记录每个节点的状态,1代表..,2代表... 
int tot=0;
void dfs(int x,int fx,int val)
{
    int temp1,temp2=-0x3f3f3f3f;//两个标记 
    if(tru[x])
    {
        temp1=0;
    }else
    {
        temp1=-0x3f3f3f3f;
    }
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to==fx)
        {
            continue;
        }
        dfs(to,x,val);
        if(sta[to]==1)//表示这个点子树中有节点还未被覆盖但需覆盖 
        {
            temp1=max(temp1,f[to]+1);//需要覆盖的点就会变多 
        }else if(sta[to]==2)//表示这个点子树中没有节点需要被覆盖而且可以向上覆盖 
        {
            temp2=max(temp2,f[to]-1);//找出最多能向上覆盖多少 
        }
    }
    if(temp1>temp2)//能覆盖别人的点覆盖不到需要被覆盖的点 
    {
        if(temp1==val)//距离恰好是二分的答案 
        {
            tot++;//增加一个 
            f[x]=val;//能覆盖的距离增大 
            sta[x]=2;//状态为2 
        }else
        {
            sta[x]=1;//未被覆盖但不必新加一个节点 
            f[x]=temp1;//记录下最大距离 
        }
    }else
    {
        sta[x]=2;//能够被覆盖 
        f[x]=temp2;//记下最大能向上覆盖多远 
    }
}
bool check(int x)
{
    tot=0;
    dfs(1,1,x);
    if(f[1]>=0&&sta[1]==1)//表示还有点未被覆盖 
    {
        tot++;//增加一个点 
    }
    if(tot>m)
    {
        return 0;
    }
    return 1;
}
int divi()//二分答案 
{
    int l=0;
    int r=n;
    int ans;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(check(mid))
        {
            ans=mid;
            r=mid-1;            
        }else
        {
            l=mid+1;
        }
    }
    return ans;
}
int main()
{
    freopen("A.in","r",stdin);
    freopen("A.out","w",stdout);
    scanf("%d%d",&n,&m);
    init();
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&tru[i]);
    }
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    printf("%d\n",divi());
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值