1. 程式人生 > >[BZOJ4883][Lydsy1705月賽]棋盤上的守衛[最小基環樹森林]

[BZOJ4883][Lydsy1705月賽]棋盤上的守衛[最小基環樹森林]

題意

有一大小為 \(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;
}