原題:
https://cses.fi/problemset/task/1675
題意:
有 n 城市與m 條雙向道路候選,每條路有修復成本。要把所有城市連通且總成本最小;若無法連通,輸出 IMPOSSIBLE。
解題思路
#include <bits/stdc++.h>
using namespace std;
struct DSU {
vector<int> p, sz;
DSU(int n=0){ init(n); }
void init(int n){
p.resize(n+1); sz.assign(n+1,1);
iota(p.begin(), p.end(), 0);
}
int find(int x){ return p[x]==x?x:p[x]=find(p[x]); }
bool unite(int a,int b){
a=find(a); b=find(b);
if(a==b) return false;
if(sz[a]<sz[b]) swap(a,b);
p[b]=a; sz[a]+=sz[b];
return true;
}
};
int main(){
int n, m;
cin>>n>>m;
struct E{int u,v; long long w;};
vector<E> edges(m);
for(auto &e: edges) cin>>e.u>>e.v>>e.w;
sort(edges.begin(), edges.end(), [](const E&a,const E&b){return a.w<b.w;});
DSU dsu(n);
long long cost=0; int used=0;
for(auto &e: edges){
if(dsu.unite(e.u,e.v)){
cost+=e.w; used++;
if(used==n-1) break;
}
}
if(used==n-1) cout<<cost<<"\n";
else cout<<"IMPOSSIBLE\n";
return 0;
}
原題:
https://cses.fi/problemset/task/1676
題意:
給 n 個城市,逐一加入 m 條無向邊。每加入一條邊後,輸出:
解題思路:
用 DSU 維護集合大小與分量數。
初始化分量數 = n,最大分量 = 1。每次 unite(u,v) 成功就分量數 -1、更新最大分量。
複雜度:O((n+m)α(n))。
#include <bits/stdc++.h>
using namespace std;
struct DSU{
vector<int> p, sz;
DSU(int n=0){ init(n); }
void init(int n){
p.resize(n+1); sz.assign(n+1,1);
iota(p.begin(), p.end(), 0);
}
int find(int x){ return p[x]==x?x:p[x]=find(p[x]); }
bool unite(int a,int b, int &mx){
a=find(a); b=find(b);
if(a==b) return false;
if(sz[a]<sz[b]) swap(a,b);
p[b]=a; sz[a]+=sz[b];
mx = max(mx, sz[a]);
return true;
}
int size(int x){ return sz[find(x)]; }
};
int main(){
int n, m;
cin>>n>>m;
DSU dsu(n);
int comps = n, mx = 1;
while(m--){
int u,v; cin>>u>>v;
if(dsu.unite(u,v,mx)) comps--;
cout << comps << " " << mx << "\n";
}
return 0;
}
原題:
https://leetcode.com/problems/min-cost-to-connect-all-points/description/
題意:
有 n 個點,連線成本為曼哈頓距離,求連通全部點的最小成本。
解題思路:
這是完全圖的 MST。可用 Prim。
示範 Kruskal:
生成所有邊(至多 n(n−1)/2 條,n=1000 時約 50 萬),排序後用 DSU 選邊。
class Solution {
public:
struct DSU{
vector<int> p, sz;
DSU(int n=0){ init(n); }
void init(int n){ p.resize(n); sz.assign(n,1); iota(p.begin(),p.end(),0); }
int find(int x){ return p[x]==x?x:p[x]=find(p[x]); }
bool unite(int a,int b){
a=find(a); b=find(b);
if(a==b) return false;
if(sz[a]<sz[b]) swap(a,b);
p[b]=a; sz[a]+=sz[b];
return true;
}
};
int minCostConnectPoints(vector<vector<int>>& points) {
int n = points.size();
struct E{int w,u,v;};
vector<E> edges; edges.reserve(n*(n-1)/2);
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
int w = abs(points[i][0]-points[j][0]) + abs(points[i][1]-points[j][1]);
edges.push_back({w,i,j});
}
}
sort(edges.begin(), edges.end(), [](const E&a,const E&b){return a.w<b.w;});
DSU dsu(n);
long long ans=0; int used=0;
for(auto &e: edges){
if(dsu.unite(e.u,e.v)){
ans+=e.w; used++;
if(used==n-1) break;
}
}
return (int)ans;
}
};
原題:
https://leetcode.com/problems/number-of-operations-to-make-network-connected/description/
題意:
有 n 台電腦與一些無向連線 connections[i] = [u, v]。
一次操作可以把多餘的線(即在同一連通分量內造成多餘、可移除的邊)重新接到兩個不同分量之間。
問最少需要幾次操作使整體連通;若不可能則回傳 -1。
解題思路:
class Solution {
public:
struct DSU{
vector<int> p, sz;
DSU(int n=0){ init(n); }
void init(int n){ p.resize(n); sz.assign(n,1); iota(p.begin(), p.end(), 0); }
int find(int x){ return p[x]==x?x:p[x]=find(p[x]); }
bool unite(int a,int b){
a=find(a); b=find(b);
if(a==b) return false;
if(sz[a]<sz[b]) swap(a,b);
p[b]=a; sz[a]+=sz[b];
return true;
}
};
int makeConnected(int n, vector<vector<int>>& connections) {
int m = (int)connections.size();
if(m < n-1) return -1; // 邊不夠,必然無解
DSU dsu(n);
for(auto &e: connections) dsu.unite(e[0], e[1]);
int comps = 0;
for(int i=0;i<n;i++) if(dsu.find(i)==i) comps++;
return comps - 1; // 需要把分量接起來
}
};