「树的直径 + 并查集」HXY造公园

HXY造公园

题目链接:HXY造公园

题目大意

给你三个数 \(n, m, q\) 代表一棵树有\(n\)个节点,再给你\(m\)行,每行两个数\(u, v\),代表\(u, v\)间有一条无向边,然后再给你\(q\)行,每行代表一个操作,第一个数如果是 1,则再输入一个数,表示查询当前点所在树的直径大小,第一个数如果是 2,则再输入两个数\(x, y\),表示查询在\(x, y\)所在树连接起来后的直径最大值最小是多少。

题目题解

很简单的题,刚看题的时候语文不太好甚至没读懂,看了下题解瞬间理解了

首先是第一个操作,考虑用并查集固定每个树的根节点,然后用树的直径算出每次以哪个根节点为起点的树的直径,每次查找只需要返回根节点就可以了

第二次操作也很简单,首先考虑一种情况就是,最大值最小,我们知道,如果两个树相连,那么两个树各一半的子树都会形成一条链,然后两两配对,怎么让其链长度最小?只需要让原一棵的树的直径/2就可以得到我们的最小链了,那么连接还需要一个长度。只有这样还不够,我们还能知道,如果连接后的链还不如其中一棵树的直径长度,那么我们两两配对的链组成的就不是我们的直径了,我们还需要和两个树原有的直径进行比较,到此完毕。(推导公式详细看代码,懒得写了..)

代码如下(

//#define fre yes

#include <cstdio>
#include <cstring>
#include <iostream>

const int N = 300005;
int head[N << 1], to[N << 1], ver[N << 1];
int par[N];
int d[N], ans[N];
bool Vis1[N], Vis2[N];

int tot;
void addedge(int x, int y) {
    ver[tot] = y;
    to[tot] = head[x];
    head[x] = tot++;
}

void init(int n) {
    for (int i = 1; i <= n; i++) {
        par[i] = i;
    }
}

int find(int x) {
    if(par[x] == x) return par[x];
    else return par[x] = find(par[x]);
}

void unite(int x, int y) {
    int a = find(x);
    int b = find(y);
    if(a == b) return ;

    par[a] = b;
}

int maxx, st;
void dfs1(int x, int val) {
    if(maxx < val) maxx = val, st = x;
    for (int i = head[x]; ~i; i = to[i]) {
        int v = ver[i];
        if(!Vis1[v]) {
            Vis1[v] = true;
            dfs1(v, val + 1);
        }
    } Vis1[x] = false;
}

void dfs2(int x, int val) {
    if(maxx < val) maxx = val;
    for (int i = head[x]; ~i; i = to[i]) {
        int v = ver[i];
        if(!Vis1[v]) {
            Vis1[v] = true;
            dfs2(v, val + 1);
        }
    } Vis1[x] = false;
}

void diameter(int n) {
    int ct = 0;
    for (int i = 1; i <= n; i++) {
        int x = find(i);
        maxx = -1;
        if(Vis2[x]) continue;
        Vis1[x] = true;
        dfs1(x, 0);

        Vis1[st] = true;
        maxx = -1;
        dfs2(st, 0);
        Vis2[x] = true;
        ans[x] = maxx;
    }
}

int main() {
    memset(head, -1, sizeof(head));
    static int n, m, q;
    scanf("%d %d %d", &n, &m, &q);
    init(n);
    for (int i = 1; i <= n; i++) d[i] = 0;
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d %d", &u, &v);
        unite(u, v);
        addedge(u, v);
        addedge(v, u);
    }

    diameter(n);

    for (int i = 1; i <= q; i++) {
        int k;
        scanf("%d", &k);
        if(k == 1) {
            int x;
            scanf("%d", &x);
            printf("%d\n", ans[find(x)]);
        }
        if(k == 2) {
            int u, v;
            scanf("%d %d", &u, &v);
            int x = find(u);
            int y = find(v);
            if(x == y) continue;
            par[x] = y;
            ans[y] = std::max(std::max((ans[x] + 1) / 2 + (ans[y] + 1) / 2 + 1, ans[x]), ans[y]);
        }
    } return 0;
}

这道题看了题解好像是卡memset,那就递归的时候同时解决吧

posted @ 2019-09-12 10:24  Nicoppa  阅读(160)  评论(0编辑  收藏  举报