题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=4883
题意:给出一个$N \times M$的棋盘,每个格子有权值。你需要每一行选中一个格子,每一列也选中一个格子(一个格子不能同时被行选中和被列选中),求这些格子权值和的最小值,$2 \leq N,M \leq 10^5 , N \times M \leq 10^5$
考虑将行与列拆成点,格子的权值变为连接其对应行与列对应节点的边,我们的问题也就是需要找到一个边集,使得每一条边都在只匹配其端点中的一个的情况下匹配到每一个点,并且边权之和最小。考虑如何找“每一条边都在只匹配其端点中的一个的情况下匹配到每一个点”的边集。我们发现一棵有$N$个节点的树可以匹配$N - 1$个点,那么我们再在树上加一条边,构成基环树,就能够满足条件了。如果我们将边变为有向边,方向向其匹配的那个点,那么满足条件的基环树就是基环外向树。所以我们的目标就是找到这个图中的最小生成基环森林,使用类似$Kruskal$的方法可以实现。
具体的实现方式在并查集上有不同。我们维护某个集合中是否有环。如果某条边对应的两个端点在同一并查集中,如果没有环则设为有环并加上边权,否则无法合并;合并时两个有环的并查集无法合并,否则合并这两个集合,并且继承有无环的状态。
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define MAXN 100010 4 using namespace std; 5 6 inline ll read(){ 7 ll a = 0; 8 char c = getchar(); 9 while(!isdigit(c)) 10 c = getchar(); 11 while(isdigit(c)){ 12 a = (a << 3) + (a << 1) + (c ^ '0'); 13 c = getchar(); 14 } 15 return a; 16 } 17 18 struct Edge{ 19 ll start , end , w; 20 }Ed[MAXN]; 21 ll fa[MAXN] , N , M; 22 bool vis[MAXN]; 23 24 bool cmp(Edge a , Edge b){ 25 return a.w < b.w; 26 } 27 28 ll find(ll x){ 29 return fa[x] == x ? x : (fa[x] = find(fa[x])); 30 } 31 32 int main(){ 33 ll ans = 0; 34 N = read(); 35 M = read(); 36 for(ll i = 1 ; i <= N ; i++) 37 for(ll j = 1 ; j <= M ; j++){ 38 Ed[(i - 1) * M + j].start = i; 39 Ed[(i - 1) * M + j].end = j + N; 40 Ed[(i - 1) * M + j].w = read(); 41 } 42 sort(Ed + 1 , Ed + N * M + 1 , cmp); 43 for(ll i = 1 ; i <= N + M ; i++){ 44 fa[i] = i; 45 vis[i] = 0; 46 } 47 for(ll i = 1 ; i <= N * M ; i++){ 48 ll p = find(Ed[i].start) , q = find(Ed[i].end); 49 if(p != q && !(vis[p] && vis[q])){ 50 fa[q] = p; 51 ans += Ed[i].w; 52 vis[p] |= vis[q]; 53 } 54 else 55 if(!vis[p]){ 56 vis[p] = 1; 57 ans += Ed[i].w; 58 } 59 } 60 cout << ans; 61 return 0; 62 }