题意 :
- 给一n个点m条边的无向图,问逐渐删去1-n点及它们的边,分别输出当前剩下多少个连通区域
思路 :
- 求连通块,想到了并查集,但并查集是增边,这里是减边,只要倒序考虑问题即可,那么问题转换为 :
- 逐渐增加n-1点,同时增加当前i点与j点 ( j > i ) (j>i) (j>i)的边,问添加i点后的连通块数量
- 暴力会tle,考虑优化方案,输入边时,
只存小的往大的边
- 对于每个逐渐加入的点i,连通块个数先加一;每增加一条边,如果另一个端点不在同一个并查集内,则连通块数量减一
- 全局变量ans记录当前连通块数量,就不用每次算一遍了
- 注意最开始还有一个0的状态,以及最后是加到2为止,而不是1(看图)
- 有一个易错点!!并查集的时候不要轻易
u=find(u)
,因为在循环里,会改变循环变量,死循环
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <unordered_set>
#include <math.h>
#define endl '\n'
#define fi first
#define se second
#define pb push_back
using namespace std;
using ll = long long;
typedef pair<int, int> PII;
const int N = 2e5 + 10;
int pa[N];
int find(int x)
{
if (pa[x] != x) pa[x] = find(pa[x]);
return pa[x];
}
int main()
{
cin.tie(nullptr) -> sync_with_stdio(false);
int n, m; cin >> n >> m;
vector<vector<int>> g(n + 1);
for (int i = 1, u, v; i <= m && cin >> u >> v; i ++ ) g[u].pb(v);
// vector<int> pa(n + 1);
for (int i = 1; i <= n; i ++ ) pa[i] = i;
vector<int> res = {0};
int ans = 0; // 当前连通块数量
for (int u = n; u >= 2; u -- )
{
ans ++ ; // 连通块+1
for (auto v : g[u]) // 枚举每条当前要加的边
{
// u = find(u), v = find(v);
int uu = find(u), vv = find(v);
if (uu != vv) // 如果当前不在同一连通块内
{
ans -- ; // 两个连通块合并,连通块个数-1
pa[uu] = vv; // 连通块合并
}
}
res.pb(ans);
}
reverse(res.begin(), res.end());
for (auto x : res) cout << x << endl;
return 0;
}