Codeforces Round #328 (Div. 2) D. Super M

时间:2022-06-01 06:40:18

题目链接:

http://codeforces.com/contest/592/problem/D

题意:

给你一颗树,树上有一些必须访问的节点,你可以任选一个起点,依次访问所有的必须访问的节点,使总路程最短。

题解:

由于是树,任意两点间路径唯一,是确定的。

首先我们要先建一颗树:包括所有必须访问的节点,已经它们之间的路径。

我们选的起点一定是某个必须访问的节点,如果我们把所有的必须访问的节点访问一遍并且回到起点,那么我们用的最小花费就是边数*2(这个可以用反证法证明),所以只要我们找出新树的直径,答案就是边数*2-直径。

(官方题解)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std; const int maxn = 2e5 + ;
const int INF = 0x3f3f3f3f; int n, m;
vector<int> G[maxn];
int ans,ans1,ans2,dis;
bool tag[maxn]; //两次dfs求最远顶点对
void dfs(int u,int fa,int d,int& res) {
if (dis < d&&tag[u]) {
dis = max(dis, d);
res = u;
}
if (dis == d&&tag[u]) res = min(res, u);
for (int i = ; i < G[u].size(); i++) {
int v = G[u][i];
if (v == fa) continue;
dfs(v, u, d + , res);
}
} bool used[maxn];
int _cnt;
//求虚拟树的节点数
bool dfs2(int u, int fa) {
if (tag[u]) used[u]=;
for (int i = ; i < G[u].size(); i++) {
int v = G[u][i];
if (v == fa) continue;
used[u] |= dfs2(v, u);
}
if (used[u]) _cnt++;
return used[u];
} int main() {
memset(tag, , sizeof(tag));
scanf("%d%d", &n, &m);
for (int i = ; i < n - ; i++) {
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
ans = INF;
for (int i = ; i < m; i++) {
int v;
scanf("%d", &v);
tag[v] = ;
ans = min(ans, v);
}
if (m == ) {
printf("%d\n%d\n", ans, );
return ;
}
dis = -, ans1 = INF;
dfs(ans, -, ,ans1);
dis = -, ans2 = INF;
dfs(ans1, -, , ans2); memset(used, , sizeof(used));
_cnt = ;
dfs2(ans1, -);
int res = (_cnt - ) * - dis;
printf("%d\n%d\n", min(ans1, ans2), res);
return ;
}