题意是要经过一棵树上的一些点(把这些点叫m点吧),可以从任意的点出发,问最少走几次边。
可以预先删除无关的节点,比如从节点1开始dfs,当某个节点为根子树中不含有m点的情况下可以删除这棵子树。然后就得到了一棵新的树,这棵树每一个叶子节点必然是m点,但是原本设定的根节点1应该有所改变。
然后以一种巨搓无比的姿势水过,各种树DP瞎搞。
最优的情况显然是走最大直径,最大直径外的边每条走两次。
至于找最小的起点可以这么搞,对于每个在最大直径上的点都标记下(注意可能有很多最大直径),然后所有标记的点中度数最小的就是起点。
#include <bits/stdc++.h>
using namespace std;
#define maxn 133333
#define maxm 266666
#define INF 11111111
int n, m;
struct node {
int from, to, next;
}edge[maxm], edge1[maxm];
int head[maxn], head1[maxn], is[maxn]; //是不是m点
int cnt, cnt1, degree[maxn]; //判断叶子节点
int num[maxn]; //以i为根的子树含有m点的个数
bool vis[maxn]; //以i为根的子树是不是删掉了
int start;
void add_edge (int from, int to, int i) {
node &e = edge[i];
e.from = from, e.to = to, e.next = head[from], head[from] = i;
}
void add_edge1 (int from, int to, int i) {
node &e = edge1[i];
e.from = from, e.to = to, e.next = head1[from], head1[from] = i;
}
void get_num (int u, int fa) {
if (is[u])
num[u]++;
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to;
if (v == fa)
continue;
get_num (v, u);
num[u] += num[v];
}
return ;
}
void del (int u, int fa) {
if (!num[u]) {
vis[u] = 1;
return ;
}
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to;
if (v == fa)
continue;
del (v, u);
}
}
int find_root (int u, int fa) {
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to;
if (v == fa || vis[v])
continue;
if (num[u] == num[v]) {
vis[u] = 1;
return find_root (v, u);
}
else return u;
}
return u;
}
void rebuild (int u, int fa) {
for (int i = head[u]; i != -1; i = edge[i].next) {
int v = edge[i].to;
if (vis[v] || v == fa)
continue;
add_edge1 (u, v, cnt1++);
add_edge1 (v, u, cnt1++);
degree[u]++;
degree[v]++;
rebuild (v, u);
}
}
int Max;
int ans[maxn][2]; //从i子树向下的最长 次长距离
int son[maxn][2]; //最长 次长的节点
int up[maxn]; //往上走的最大长度
void dfs (int u, int fa) {
for (int i = head1[u]; i != -1; i = edge1[i].next) {
int v = edge1[i].to;
if (vis[v] || v == fa)
continue;
dfs (v, u);
if (ans[v][0]+1 > ans[u][0]) {
ans[u][1] = ans[u][0];
ans[u][0] = ans[v][0]+1;
son[u][1] = son[u][0];
son[u][0] = v;
}
else if (ans[v][0]+1 > ans[u][1]) {
ans[u][1] = ans[v][0]+1;
son[u][1] = v;
}
}
Max = max (Max, ans[u][0]+ans[u][1]);
}
bool mark[maxn]; //是不是最大直径上面的点
void find_start (int u, int fa) {
if (ans[u][0]+ans[u][1] == Max) {
mark[u] = 1;
}
else if (fa) {
if (up[u]+ans[u][0] == Max)
mark[u] = 1;
}
for (int i = head1[u]; i != -1; i = edge1[i].next) {
int v = edge1[i].to;
if (v == fa || vis[v])
continue;
if (v == son[u][0]) {
up[v] = max (up[u], ans[u][1])+1;
}
else up[v] = max (up[u], ans[u][0])+1;
find_start (v, u);
}
}
int main () {
//freopen ("in", "r", stdin);
scanf ("%d%d", &n, &m);
memset (head, -1, sizeof head);
memset (is, 0, sizeof is);
cnt = 0;
int u, v;
for (int i = 1; i < n; i++) {
scanf ("%d%d", &u, &v);
add_edge (u, v, cnt++);
add_edge (v, u, cnt++);
}
memset (num, 0, sizeof num);
for (int i = 1; i <= m; i++) {
scanf ("%d", &u);
is[u] = 1;
}
get_num (1, 0);
memset (vis, 0, sizeof vis);
del (1, 0);
int root = find_root (1, 0); //重新确定根
memset (head1, -1, sizeof head1);
memset (degree, 0, sizeof degree);
memset (mark, 0, sizeof mark);
cnt1 = 0;
rebuild (root, 0); //新建一棵树
cnt1 >>= 1; //新树的边
Max = 0;
memset (ans, 0, sizeof ans);
dfs (root, 0); //最长直径
find_start (root, 0);
for (int i = 1; i <= n; i++) {
if (mark[i] && (degree[i] == 1|| degree[i] == 0)) {
printf ("%d\n", i);
break;
}
}
printf ("%d\n", Max+(cnt1-Max)*2);
return 0;
}