目录
一.题目
1.描述
给出一颗 N 个节点的树和 M 个关键节点以及无线数据传输装置的数量 K, 树边长度为 1。 你需要把这 K 个无线数据传输装置安装到树的一些节点上, 并让关键节点离最近无线 传输装置的距离最大值最小, 求出这个最小值。 此处, 树上节点 A 和 B 之间的距离指 AB 两点间路径长度和。
2.输入
第一行输入三个正整数 N, M 和 K。 接下来一行输入 M 个正整数, 表示关键点的编号。 接下来 N-1 行, 每行两个正整数 a, b, 表示节点 a 和节点 b 之间有一条边。
3.输出
输出一行一个正整数表示你的答案。
4.样例输入
5 2 1
1 4
1 2
1 3
2 4
4 5
5.样例输出
1
二.题解
1.算法
乍一看,这个答案是具有单调性的,并且有确切的范围(0~n-1),果断用二分答案的方法虽然考试时没想到。
但是二分里面的check怎么写呢?这是最大的难点。
我是这样想的:
搜索这一颗树,在回溯的时候判断以这个点为根的子树里离这个点最远的且没被覆盖的关键点的距离,如果这个距离和你二分限制的距离相等了,那么你就必须在这个点放一个基站。就这样做下去,如果最终用的基站数量大于了k,就否定这个二分的限制。
举个例子(就拿样例来说):
(标橙的是关键节点)假设限制距离为1,从根节点1开始搜,先搜到2,再往下搜,搜到4,再往下搜,搜到5,这是考虑5的回溯,因为在5的子树里没有关键节点,所以这时最远关键结点的距离还是0;回溯到4,发现只有4它本生是关键节点,这是关键节点的距离还是0;回溯到2,关键节点的最远距离就变成了1,这1等于了我们限制的距离,并且离这个点距离为1的范围内没有基站,所以必须在节点2放1个基站;回溯到节点1,再往节点3搜,节点3不是关键节点,直接回溯,发现节点1是一个关键节点,并且离这个点距离为1的范围有基站,所以不管它。最终就设了一个基站,最短距离为1.
2.细节
这道题不光要会算法,细节也特别多。主要分布在check内的dfs里边。
1.当当前节点的子树里的关键节点都被覆盖或没有时,我把纪录关键节点的距离的变量(max_d)标为-1,只有当max_d不为-1时,回溯时才能加1:
if (max_d != -1) max_d ++;
2.如果当前节点是关键节点,在回溯时要注意,如果max_d不为-1,就不管它,否则要把max_d赋为0:
if (vis[x]) max_d = max (max_d, 0);
3.用min_k记录最近的一个基站的距离,如果关键节点和最近的基站在树同侧,限制的距离-min_k=限制的距离-当前关键节点到最近基站的距离,如果减出来大于等于0,就行,因为当前关键节点的最远距离为0;如果关键节点和最近基站在异侧,画一张图就理解了:
(回溯到节点1)min_k = 节点1到节点2的距离,当前关键节点的最远距离为1,限制的最远距离-min_k1就行。
if (now_way - min_k >= max_d) max_d = -1;
4.如果当前最远的关键节点距离等于了限制距离,就必须在这里放基站
if (now_way == max_d){
max_d = -1;
min_k = 0;
cnt ++;
}
其他的就没有什么想说的了,这道题目就解决了。
三.代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
using namespace std;
#define M 200005
int n, m, k, gj, ans = 0x3f3f3f3f, now_way, cnt;
vector <int> G[M];
bool vis[M];
pair <int, int> dfs (int x, int fa){
int min_k = 0x3f3f3f3f, max_d = -1;
for (int i = 0; i < G[x].size (); i ++){
int u = G[x][i];
if (u != fa){
pair <int, int> tmp = dfs (u, x);
min_k = min (min_k, tmp.first), max_d = max (max_d, tmp.second);//找关键节点最大距离和最近基站的距离
}
}
min_k ++;
if (max_d != -1) max_d ++;
if (vis[x]) max_d = max (max_d, 0);
if (now_way - min_k >= max_d) max_d = -1;
if (now_way == max_d){
max_d = -1;
min_k = 0;
cnt ++;
}
return make_pair (min_k, max_d);
}
bool check (int max_way){
cnt = 0;
now_way = max_way;
pair <int, int> tmp = dfs (1, 0);
if (tmp.second != -1)
cnt ++;
if (cnt > k)
return 0;
return 1;
}
int main (){
scanf ("%d %d %d", &n, &m, &k);
for (int i = 1; i <= m; i ++){
scanf ("%d", &gj);
vis[gj] = 1;
}
int a, b;
for (int i = 1; i < n; i ++){
scanf ("%d %d", &a, &b);
G[a].push_back (b);
G[b].push_back (a);
}
int l = 0, r = n - 1, mid;
while (l <= r){//二分
mid = (l + r) / 2;
if (check (mid)){
ans = min (ans, mid);
r = mid - 1;
}
else
l = mid + 1;
}
printf ("%d\n", ans);
return 0;
}