【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; }