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;
}