cf592d

题意:给出一个无根树,点数为10^5,所有边的长度为1。给定其中有一些点是受到攻击的。

现在要求一个人选定一个点作为起点,走遍所有的受攻击点(不用再回到起点)。

需要的最短距离是多少,选定的起点是哪个。

分析:这是一个求树直径的变形题,原版请看这里

求树的直径即树中最远的点对,需要进行两次bfs或者dfs。这里我们用的是dfs。

首先我们假设所有点都受到攻击,那么要走完所有点再回到起点,路程L就是树上所有边的长度和乘以2。

不用回到起点遍历所有点的最短距离就是用这个L减去树上的最远两点之间的距离。

我们要把起点选在最远点对中的点才能保证所得解最小。

但本题中不是所有点都受到攻击,所以在求最远点对的时候要进行一些改变。要找最远的受攻击点对。

首先要以一个受攻击点为根(这个是本题最大难点,不太容易想到),进行第一次dfs,找到最深的受攻击点之后,再以它为根找最深的受攻击点。(注意长度相同时,找编号最小的)

再求出以任意受攻击点为根,遍历所有受攻击点的路程和。两者相减即为解。

#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;

const int MAX_N = (int)(2e5) + 20;

int n, m;
int root;
vector<int> g[MAX_N];
bool attacked[MAX_N];
int depth[MAX_N];
long long path_len;

void input()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n - 1; i++)
    {
        int a, b;
        scanf("%d%d", &a, &b);
        a--;
        b--;
        g[a].push_back(b);
        g[b].push_back(a);
    }
    memset(attacked, 0, sizeof(attacked));
    for (int i = 0; i < m; i++)
    {
        int a;
        scanf("%d", &a);
        a--;
        attacked[a] = true;
    }
}

void dfs(int u, int father)
{
    for (int i = 0; i < (int)g[u].size(); i++)
    {
        int v = g[u][i];
        if (v == father)
            continue;
        depth[v] = depth[u] + 1;
        dfs(v, u);
    }
}

int find_next()
{
    int ret = root;
    for (int i = 0; i < n; i++)
    {
        if (!attacked[i])
            continue;
        if (depth[i] > depth[ret] || (depth[i] == depth[ret] && i < ret))
        {
            ret = i;
        }
    }
    return ret;
}

bool cal_depth(int u, int father)
{
    bool ret = false;
    for (int i = 0; i < (int)g[u].size(); i++)
    {
        int v = g[u][i];
        if (v == father)
            continue;
        if (!cal_depth(v, u))
            continue;
        path_len += 2;
        ret = true;
    }
    return ret || attacked[u];
}

int main()
{
    input();
    root = find(attacked, attacked + n, true) - attacked;

    depth[root] = 0;
    dfs(root, -1);
    root = find_next();

    depth[root] = 0;
    dfs(root, -1);

    int end = find_next();
    int city = min(root, end);
    path_len = 0;
    cal_depth(root, -1);
    printf("%d\n%I64d\n", city + 1, path_len - depth[end]);
    return 0;
}

时间: 2025-01-12 09:29:33

cf592d的相关文章

树的直径-CF592D Super M

给定一颗n个节点树,边权为1,树上有m个点被标记,问从树上一个点出发,经过所有被标记的点的最短路程(起终点自选).同时输出可能开始的编号最小的那个点.M<=N<=123456. 先想:如果所有点都被标记那么怎么样?我们发现对于起点s终点t,如果它们在同一条链上,那么必须先从s往外走,再回来,再经过t,再回到t.走过的路径就是树上所有边*2-s到t的路径.如果它们不在同一条链上,那么s在走到t的过程中访问所有点,走过的路径还是树上所有边*2-s到t的路径. 于是如果所有点都被标记,我们应该找到树

[CF592D]Super M

题目大意: 给定一颗n个节点树,边权为1,树上有m个点被标记,问从树上一个点出发,经过所有被标记的点的最短路程,以及可行的最小的端点编号.(起终点自选) M<=N<=123456 思路: 随便定一个标记节点为根,然后以该节点开始遍历,将不是标记节点的叶节点剪掉,剩下的边数为P.求出树的直径L.答案即为2*P-L. 另外注意一定要保证节点编号最小. 1 #include<cstdio> 2 #include<cctype> 3 #include<vector>