[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;
}
posted @ 2018-10-17 14:50  fwat  阅读(179)  评论(0编辑  收藏  举报