BZOJ4883 棋盘上的守卫 最小基环树

给一个 N × M ≤ 1 e 5 N×M\leq1e5 N×M1e5的棋盘,然后每个棋盘都可以放一个守卫,你可以选择是让它保护横向的也可以让他保护纵向的。然后一个位置最多只能放一个守卫,放置在 ( i , j ) (i,j) (i,j)这个位置的代价是 w i , j w_{i,j} wi,j。然后求使得所有的位置都被守护的最小代价。
完全不太会做的题。
一种费用流/最大权匹配的做法是左边放 N + M N+M N+M个点分别表示行/列,右边是所有的坐标,中间连代价大小的边,求最大权匹配。
但复杂度会比较大,这里的做法是把 i i i j j j w i , j w_{i,j} wi,j大小的边。然后求一个基环树,因为相当于每个点都要被选到,并且入度为 1 1 1
求基环树用克鲁斯卡尔维护。
如果当前两个点在同一集合,那么判断是否已经成环,如果不成环还可以加上这一条边。
如果当前两个点不在同一个集合,那么判断是否存在一个点所在集合没有成环,如果是可以加边。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf=0x3f3f3f3f;
const int N=2e5+7; 
struct edge { 
	int u,v,w; 
	bool operator <(const edge &rhs) const {
		return w<rhs.w;
	}
}e[N];
bool cir[N];
int fa[N]; 
int tot=0; 
int find(int x) {
	if(x==fa[x]) return fa[x];
	else return fa[x]=find(fa[x]); 
}
int main() { 
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=m;j++) {
			int w;
			scanf("%d",&w);
			e[++tot].u=i;
			e[tot].v=n+j;
			e[tot].w=w;
		} 
	}
	sort(e+1,e+1+tot);
	ll ans=0;
	for(int i=1;i<=n+m;i++) fa[i]=i;
	for(int i=1;i<=tot;i++) {
		int U=find(e[i].u);
		int V=find(e[i].v);
		if(U==V) {
			if(!cir[U]) {
				cir[U]=1; 
				ans+=e[i].w;
			}
		}
		else if(!cir[U]||!cir[V]) {
			fa[U]=V;
			ans+=e[i].w;
			cir[V]|=cir[U]; 
		}
	}
	printf("%lld\n",ans);  
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值