前言
A完了发现标签里有一个“dsu”,这个疑问感觉是要留着过年。
参考博客
这个博客写了5个版本的代码,深刻揭示了“stl到底还有多少我不会用的东西”这个话题。
正文
一、题意
一张图中有n个点。给出m个点对,每个点对由x,y两个数字组成,表示x,y之间没有边相连。若x,y(或y,x)点对没有出现过,说明x,y之间有边存在。n,m不超过200000,同时m不超过(n + 1) * n / 2。
求出最大连通分量的数量,以及每个连通分量中节点的个数。
二、思路
对每个节点进行搜索。用vector维护“未被访问过的点”,搜索时寻找和当前节点有边相连的点,进行访问,访问过就从容器中删除。“寻找和当前节点有边相连的点”这个操作,只要遍历容器中所有剩下的点,与当前点有点对,则跳过,否则就是“有边相连”。
这么一想,如果一条边都没有,复杂度岂不是o(n * n)直接炸?
欸,当然不会炸。m是有上限的,当一条边都没有的时候,(n + 1) * n / 2还不到200000,即使n * n的复杂度又如何?
而实际上,这样的做法,会重复进行的操作只有o(m + n)次。因为只有m个点对,我们会跳过而导致重复访问的只能是O(m)次。而当我们对某个点不跳过时,它就会从容器中被删除。在对容器中的点顺序尝试搜索的时候,最终搜索的复杂度首先是“每个点都会被搜到一次”,这是o(n)的复杂度,然后是“有一些点会重复地被搜到”,这里是o(m)的复杂度。
三、代码
#include <bits/stdc++.h>
using namespace std;
const int mx = 2e5 + 10;
int n, m;
map<int, bool> mp[mx];
vector<int> s;
priority_queue<int, vector<int>, greater<int> > ans;
void bfs(int x) {
// cout << "go bfs" << endl;
queue<int> q;
q.push(x);
swap(s[0], s.back());
s.pop_back();
int num = 0;
while(!q.empty()) {
num++;
int cur = q.front(); q.pop();
// cout << "bfs x = " << x << " cur = " << cur << endl;
for (int i = 0; i < s.size(); i++) {
if(!mp[cur].count(s[i])) {//cur到s[i]存在边
q.push(s[i]);
//通过这样的方法实现从容器中删元素,更快
swap(s[i], s.back());
s.pop_back();
i--;
}
}
// for (int i = 1; i <= n; i++) {
// if (s.count(i)) { //找到未访问的i
// if (!mp[cur].count(i)) { //如果cur到i存在边连接
// s.erase(i); //把i归为未访问
// q.push(i); //继续搜i,找到和i相连的点
// }
// }
// }
}
ans.push(num);
}
int main() {
scanf("%d %d", &n, &m);
while(m--) {
int x, y;
scanf("%d %d", &x, &y);
mp[x][y] = true;
mp[y][x] = true;
}
for (int i = 1; i <= n; i++)
s.push_back(i);
//搜索,记录每个点形成连通块的大小
while(!s.empty()) {
bfs(s[0]);
}
//输出答案
printf("%d\n", ans.size());
while(!ans.empty()) {
printf("%d", ans.top());
ans.pop();
if(ans.empty()) printf("\n");
else
printf(" ");
}
return 0;
}