逆向思维 + 用并查集动态维护连通块的个数——Luogu P1197题解

题目大意:

给你一个 $ n $ 个点的图与 $ m $ 条边,接下来给出一个长度为 $ k $ 个整数,按照给出整数的顺序依次删掉对应编号的点,求出一开始的连通块的个数与接下来每次删除一个点后的连通块的个数。(连通块就是一个点集,这个集合里面的任意两个点都可以互相到达)

思路:

大体思路:

这个题目如果正向考虑会很难做,因为我们要每次维护一个不断删点的图的连通块个数是很难弄的。所以我们要倒着考虑。假如我们把删点的过程倒着考虑,就变成了从最后一个点往前添加进这个图里面的过程。所以这样问题就变成了对一个图每次添加进一个点和从这个点连出去的一些的边,求出每一次添加后的图的联通块的个数。

怎么用并查集维护连通块的个数:

我们首先先让每个点都是独立的,之后我们再按照给出的关系用并查集合并。如果两个点可以合并,说明这两个点之前再不同的连通块里,现在这两个连通块之间连通了,连通块的个数就减一。如果这两个点不用进行合并,说明这两个点已经在一个连通块里了,连通块的个数就保持不变。

代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>

using namespace std;

const int N = 5e5 + 5;

int n, m, k;
int u, v;
int num = 0; //连通块的个数
int arr[N];
int f[N]; //并查集
bool Is_Delete[N]; //标记那些点要删除

vector <int> to[N];
vector <int> ans; //存储答案
vector <int> point_rest; //被删剩下的点

int get_f(int v) {
    if (f[v] == v) return v;
    else return f[v] = get_f(f[v]);
}

void merge(int u, int v) {
    int t1 = get_f(u);
    int t2 = get_f(v);
    if (t1 != t2) f[t2] = t1;
}

int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') w = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        s = s * 10 + ch - '0';
        ch = getchar();
    }
    return s * w;
}

void write(int x) {
    if (x < 0) putchar('-'), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

int main() {
    n = read(), m = read();
    for (register int i = 1; i <= m; ++i) {
        u = read(), v = read();
        to[u].push_back(v);
        to[v].push_back(u);
    }
    k = read();
    for (register int i = 1; i <= k; ++i) {
        arr[i] = read();
        Is_Delete[arr[i]] = true; //标记要删除的点
    }
    for (register int i = 0; i < n; ++i) {
        f[i] = i; //初始化并查集
        if (!Is_Delete[i]) {
            ++num;
            point_rest.push_back(i);
        }
    }
    vector <int> :: iterator it;
    for (it = point_rest.begin(); it != point_rest.end(); ++it) {
        vector <int> :: iterator it_tmp;
        for (it_tmp = to[*it].begin(); it_tmp != to[*it].end(); ++it_tmp) {
            if (!Is_Delete[*it_tmp]) {
                int t1 = get_f(*it);
                int t2 = get_f(*it_tmp);
                if (t1 != t2) {
                    merge((*it), (*it_tmp));
                    --num;
                }
            }
        }
    }
    for (register int i = k; i >= 1; --i) {
        ans.push_back(num);
        ++num; //添加进一个还没被合并的点连通块数量增加
        Is_Delete[arr[i]] = false;
        for (it = to[arr[i]].begin(); it != to[arr[i]].end(); ++it) {
            if (!Is_Delete[*it]) {
                int t1 = get_f(arr[i]);
                int t2 = get_f(*it);
                if (t1 != t2) {
                    merge(arr[i], (*it));
                    --num;
                }
            } 
        }
    }
    ans.push_back(num);
    for (register int i = (int)ans.size() - 1; i >= 0; --i) 
        write(ans[i]), putchar('\n');
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/lixiao189/p/9752082.html