题意
完全图。
告诉你
m
m
m条边权值为
1
1
1,其他为
0
0
0
求最小生成树权值。
题解
容易发现,如果从每个点只跑
0
0
0边,最后会形成几个连通块,答案就是连通块个数减一。
那么怎么知道几个连通块呢?
首先我想的是
d
f
s
dfs
dfs+链表优化,然而复杂度过于玄学,因为删除的顺序不清楚,所以也并没有通过。
d
f
s
dfs
dfs实际是可以通过的,预存那些能跑的点,把不能跑的点作为新的集合传下去(否则都跑过了)。这样是人为规定了顺序,很优秀的一个做法。
但是这类无向图
O
(
n
)
O(n)
O(n)遍历其实是有标准做法的,
b
f
s
bfs
bfs。
用
s
e
t
set
set存权值
1
1
1的图,再存一个集合,每次到每个点遍历集合,如果发现图里没有,加入队列,从集合中删除这个点。
复杂度是,每个点只会遍历一次,每个点会多判断那些权值为
1
1
1的边。
O
(
(
n
+
m
)
l
o
g
n
)
O((n+m)logn)
O((n+m)logn)
之前做过类似的题目,也是 b f s bfs bfs+链表优化处理的, b f s bfs bfs人为规定了删除顺序,反倒是不会错乱。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+500;
int n,m;
set<int>G[maxn];
bool vis[maxn];
int lst[maxn],rst[maxn];
set<int>T;
void bfs(int u){
queue<int>q;
q.push(u);
T.erase(u);
vis[u]=1;
while(!q.empty()){
int now=q.front();q.pop();
auto iter=T.begin();
vector<int>tmp;
for(auto iter=T.begin();iter!=T.end();){
int to=*iter;
iter++;
if(G[now].find(to)==G[now].end()){
tmp.push_back(to);
vis[to]=1;
q.push(to);
}
}
for(auto it:tmp)T.erase(it);
}
}
int main(){
cin>>n>>m;
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
G[u].insert(v);
G[v].insert(u);
}
for(int i=1;i<=n;i++)T.insert(i);
int cnt=0;
for(int i=1;i<=n;i++){
if(vis[i])continue;
bfs(i);
cnt++;
}
cout<<cnt-1<<endl;
}