[BZOJ4883][Lydsy1705月賽]棋盤上的守衛[最小基環樹森林]
阿新 • • 發佈:2018-11-11
題意
有一大小為 \(n*m\) 的棋盤,要在一些位置放置一些守衛,每個守衛只能保護當前行列之一,同時在每個格子放置守衛有一個代價 \(w\) ,問要使得所有格子都能夠被保護,需要最少多少的代價。
\(2\leq n,m\leq 10^5\ ,n*m\leq 10^5\)
分析
將行列看成 \(n+m\) 個點。將每個格點放置守衛看成所在行列連了一條邊,然後把每條邊定向,如果被指向表示當前格點對當前 行/列 進行了保護。
這樣就會有 \(n+m\) 個點,\(n+m\) 條有向邊,同時每條邊最多有 1 的入度。形成了基環樹森林。
最小基環樹森林可以通過 \(Kruskal\) 貪心求解,證明仍然可以考慮反證法。
總時間複雜度為 \(O(n)\) 。
程式碼
#include<bits/stdc++.h> using namespace std; typedef long long LL; const int N=2e5 + 7; int n,m,edc; int par[N],c[N]; struct edge{ int last,to,dis; edge(){}edge(int last,int to,int dis):last(last),to(to),dis(dis){} bool operator <(const edge &rhs)const{ return dis<rhs.dis; } }e[N*2]; int getpar(int a){return par[a]==a?a:par[a]=getpar(par[a]);} LL Kruskal(){ sort(e+1,e+1+edc);int cnt=0;LL res=0; for(int i=0;i<N;i++) par[i]=i; for(int i=1;i<=edc;i++){ int x=getpar(e[i].last),y=getpar(e[i].to);0 if(x==y&&!c[x]) c[x]=1,cnt++,res+=e[i].dis; if(x!=y&&!(c[x]&&c[y])) par[x]=y,c[y]|=c[x],cnt++,res+=e[i].dis; if(cnt==n+m) return res; } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) for(int j=1,x;j<=m;j++){ scanf("%d",&x); e[++edc]=edge(i,j+n,x); } printf("%lld\n",Kruskal()); return 0; }