题目链接:
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 + 10; 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 = 0; i < G[u].size(); i++) { int v = G[u][i]; if (v == fa) continue; dfs(v, u, d + 1, res); } } bool used[maxn]; int _cnt; //求虚拟树的节点数 bool dfs2(int u, int fa) { if (tag[u]) used[u]=1; for (int i = 0; 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, 0, sizeof(tag)); scanf("%d%d", &n, &m); for (int i = 0; i < n - 1; i++) { int u, v; scanf("%d%d", &u, &v); G[u].push_back(v); G[v].push_back(u); } ans = INF; for (int i = 0; i < m; i++) { int v; scanf("%d", &v); tag[v] = 1; ans = min(ans, v); } if (m == 1) { printf("%d %d ", ans, 0); return 0; } dis = -1, ans1 = INF; dfs(ans, -1, 0,ans1); dis = -1, ans2 = INF; dfs(ans1, -1, 0, ans2); memset(used, 0, sizeof(used)); _cnt = 0; dfs2(ans1, -1); int res = (_cnt - 1) * 2 - dis; printf("%d %d ", min(ans1, ans2), res); return 0; }