题意:
给你一棵n个点的树(n<=100),每一个点有白/黑色,让你选m个黑色的点,
使得你选的这m个点的集合里最远的两个点的距离最小
解析:
这道题我训练的时候是用st的LCA求两点距离+二分+最大团验证来做的,代码有167行
比赛的时候...估计得写将近1个小时,然后还被自己LCA模板上的一个数组大小卡了半个小时...
这道题赛后看了大佬们的代码,大多都是和树的直径联系在一起的。
可以看一下树的直径及其证明。
里面有一个很重要的性质,就是树上一个点x最远能到达的点一定是直径的一个端点
这道题做法很多,首先一个比较简单版本的就是枚举任意两个点x,y,记录他们的距离为最长距离res
然后把剩余的点k加进来,如果dis[x][k]<=res&&dis[k][y]<=res,那么这个点就是可以加入的
如果最后的点数>=m,那么对答案进行更新
这里为什么点k满足dis[x][k]<=res&&dis[k][y]<=res就可以加入进来,保证k与集合里面的其他点的距离都<=res?
那么下面是证明
假定我们枚举的边是st,然后x,y都加入了集合
su=编号1,uv=编号5,vt=编号2,ux=编号4,vy=编号3
那么x,y加入集合条件是1+4<=1+5+2, 4+5+2<=1+5+2
=>4<5+2 && 4<=1
同理3<=5+1 && 3<=2
那么我们证明4+3+5的长度
4+3+5(xy)<= 1+3+2(st)
那么就满足了条件了
所以这个思想得到的一个结论是
一条树链xy的长度为p,,如果两个点s,t都满足dis[s/t][x]<=p&&dis[s/t][y]<=p
那么dis[s][t]一定满足<=p
代码来源于Engineering Drawing
#include <bits/stdc++.h>
using namespace std;
const int N = 100 + 5;
vector<int> G[N];
int dis[N][N], level[N], col[N], n, m;
void addedge(int u, int v) {
G[u].push_back(v);
G[v].push_back(u);
}
void bfs(int s) {
memset(level, -1, sizeof level);
queue<int> q;
level[s] = 0;
q.push(s);
while(!q.empty()) {
int u = q.front(); q.pop();
for(int v : G[u])
if(level[v] == -1) {
level[v] = level[u] + 1;
q.push(v);
}
}
for(int i = 1; i <= n; i++)
dis[s][i] = level[i];
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> col[i];
for(int i = 1; i <= n - 1; i++) {
int u, v; cin >> u >> v;
addedge(u, v)