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

题目链接:

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\n%d\n", 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\n%d\n", min(ans1, ans2), res);
    return 0;
}

 

posted @ 2016-06-09 16:18  fenicnn  阅读(204)  评论(0编辑  收藏  举报