【bzoj4883】[Lydsy2017年5月月赛]棋盘上的守卫 最小环套树森林

题目描述

在一个n*m的棋盘上要放置若干个守卫。对于n行来说,每行必须恰好放置一个横向守卫;同理对于m列来说,每列必须恰好放置一个纵向守卫。每个位置放置守卫的代价是不一样的,且每个位置最多只能放置一个守卫,一个守卫不能同时兼顾行列的防御。请计算控制整个棋盘的最小代价。

输入

第一行包含两个正整数n,m(2<=n,m<=100000,n*m<=100000),分别表示棋盘的行数与列数。
接下来n行,每行m个正整数
其中第i行第j列的数w[i][j](1<=w[i][j]<=10^9)表示在第i行第j列放置守卫的代价。

输出

输出一行一个整数,即占领棋盘的最小代价。

样例输入

3 4
1 3 10 8
2 1 9 2
6 7 4 6

样例输出

19


题解

最小环套树森林

首先一眼费用流,然而数据量过大直接卡掉(同时卡掉的还有zkw费用流= =)(跪烂那些用KM算法水过的dalao。。。)

然后经过观察可以发现,如果在行列之间连边,那么答案构成的一定是一个环套树森林。

证明:设行数+列数为n,则构成的图中,点数和边数都为n。如果把每条边选择的方案看作是边的方向的话(a/b中选a看作a->b),那么每个点的出度一定均为1。这样的图一定是环套树森林。因此命题得证。

然后要求的就是无向图的最小环套树森林。

很容易发现环套树森林也是一个拟阵,拟阵最优化问题即可使用贪心算法(Kruscal)求解。

那么本题就和求最小生成树的方法一样了,按边权排序,从小到大加。只需要在原并查集的基础之上,维护每个连通块是否有环,连边时判断即可。

时间复杂度$O(nm\log nm)$

#include <cstdio>
#include <algorithm>
#define N 100010
using namespace std;
struct data
{
	int x , y , z;
	bool operator<(const data &a)const {return z < a.z;}
}a[N];
int f[N] , c[N];
int find(int x)
{
	return x == f[x] ? x : f[x] = find(f[x]);
}
int main()
{
	int n , m , i , j , tx , ty;
	long long ans = 0;
	scanf("%d%d" , &n , &m);
	for(i = 1 ; i <= n ; i ++ )
		for(j = 1 ; j <= m ; j ++ )
			scanf("%d" , &a[(i - 1) * m + j].z) , a[(i - 1) * m + j].x = i , a[(i - 1) * m + j].y = j + n;
	sort(a + 1 , a + n * m + 1);
	for(i = 1 ; i <= n + m ; i ++ ) f[i] = i;
	for(i = 1 ; i <= n * m ; i ++ )
	{
		tx = find(a[i].x) , ty = find(a[i].y);
		if(tx == ty && !c[tx]) c[tx] = 1 , ans += a[i].z;
		if(tx != ty && !(c[tx] && c[ty])) f[tx] = ty , c[ty] |= c[tx] , ans += a[i].z;
	}
	printf("%lld\n" , ans);
	return 0;
}

 

 

posted @ 2017-09-08 10:02  GXZlegend  阅读(476)  评论(0编辑  收藏  举报