题意:
给定一棵树和一个整数
k
k
k,可以在树的任意位置放置一个人,这个人可以监管距离其所在节点不超过
k
k
k的所有节点,问最少放置多少个人可以监管整棵树的所有节点。
思路:
此题的解题思路还挺具有启发意义。
对于树的相关问题,我们可以考虑先转换成线性问题来做,如果是线性的话,很显然最优解应该是从某一端的边界开始,每个长度为 2 k 2k 2k的区间的中心均放置一个人,若最后的距离不足 2 k 2k 2k,则也在其中心放置一个人。
我们可以推广贪心的思想到树形结构上:
对于这棵树,我们可以从叶子节点出发,定义
d
p
[
i
]
dp[i]
dp[i]:
若
d
p
[
i
]
>
0
dp[i]>0
dp[i]>0:则代表以
i
i
i为根节点的子树可以向上监管距离不超过
d
p
[
i
]
dp[i]
dp[i]的节点
若
d
p
[
i
]
<
0
dp[i]<0
dp[i]<0:则代表以
i
i
i为根节点的子树需要其他节点帮助其监管距离为
−
d
p
[
i
]
-dp[i]
−dp[i]的节点
对于当前考虑的节点
i
i
i,我们可以求出其子节点
d
p
dp
dp值的最小值
M
n
Mn
Mn和最大值
M
x
Mx
Mx,随后分以下情况讨论:
若
i
i
i为叶子节点,则说明需要其他节点帮助提供监管,更新
d
p
[
i
]
=
−
1
dp[i] = -1
dp[i]=−1。
若
M
n
=
=
−
k
Mn == -k
Mn==−k说明存在某一个以
i
i
i的子节点为根的子树,需要其他节点帮助提供
k
k
k个单位的监管距离,故由
i
i
i提供,更新
d
p
[
i
]
=
k
dp[i] = k
dp[i]=k。
若
M
n
+
M
x
−
1
>
=
0
Mn + Mx - 1 >= 0
Mn+Mx−1>=0 则说明有一个子树放置的人能够把其他全部需要监管的村庄都覆盖,则更新
d
p
[
i
]
=
M
x
−
1
dp[i] = Mx - 1
dp[i]=Mx−1
若以上情况均不满足,则说明可以继续向上查找更优的放置点,更新
d
p
[
i
]
=
M
n
−
1
dp[i] = Mn - 1
dp[i]=Mn−1
故此题得解。
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int INF = 1e8 + 10;
const int A = 2e5 + 10;
class Gra{
public:
int v,next;
}G[A<<2];
int head[A],dp[A],n,k,tot,ans;
void add(int u,int v){
G[tot].v = v;
G[tot].next = head[u];
head[u] = tot++;
}
void dfs(int u,int pre){
int Mn = INF,Mx = -INF;
for(int i=head[u] ;i!=-1 ;i=G[i].next){
int v = G[i].v;
if(v == pre) continue;
dfs(v,u);
Mn = min(Mn,dp[v]);
Mx = max(Mx,dp[v]);
}
if(Mn == INF) dp[u] = -1;
else if(Mn <= -k){
dp[u] = k;
ans++;
}
else if(Mn + Mx > 0) dp[u] = Mx - 1;
else dp[u] = Mn - 1;
}
int main(){
memset(head,-1,sizeof(head));
tot = 0;
scanf("%d%d",&n,&k);
if(k == 0) printf("%d\n",n);
else{
for(int i=1 ;i<n ;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
memset(dp,0,sizeof(dp));
ans = 0;
dfs(1,1);
if(dp[1] < 0) ans++;
printf("%d\n",ans);
}
return 0;
}