【bzoj1059】[ZJOI2007]矩阵游戏 二分图最大匹配

题目描述

小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏——矩阵游戏。矩阵游戏在一个N*N黑白方阵进行(如同国际象棋一般,只是颜色是随意的)。每次可以对该矩阵进行两种操作:行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色)列交换操作:选择矩阵的任意行列,交换这两列(即交换对应格子的颜色)游戏的目标,即通过若干次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。对于某些关卡,小Q百思不得其解,以致他开始怀疑这些关卡是不是根本就是无解的!!于是小Q决定写一个程序来判断这些关卡是否有解。

输入

第一行包含一个整数T,表示数据的组数。接下来包含T组数据,每组数据第一行为一个整数N,表示方阵的大小;接下来N行为一个N*N的01矩阵(0表示白色,1表示黑色)。

输出

输出文件应包含T行。对于每一组数据,如果该关卡有解,输出一行Yes;否则输出一行No。

样例输入

2
2
0 0
0 1
3
0 0 1
0 1 0
1 0 0

样例输出

No
Yes


题解

二分图最大匹配

本题和“给你一些黑色格子,问能否选出n个,使得每行、每列有且仅有一个黑色格子”是相同的。

证明:

必要性:假设不能使得每行、每列有且仅有至少一个黑色格子,那么一定存在某行或列不存在黑色格子,无论如何交换,该行或列都不存在黑色格子,故无解。必要性证毕。

充分性:如果存在某种选择方式使得每行、每列都有且仅有一个黑色格子,那么我们只考虑这n个格子,无论如何交换它们的行或列,每行、每列依然都有且仅有一个黑色格子。从1到n考虑,对于第i行,如果该行不合法,那么一定存在i+1~n行的某行中第i列为黑色。此时只需要交换这两列即可。进行到第n行时,由于前n-1行和前n-1列都已经有黑色格子,那么最后一个黑色格子一定存在于第n行第n列。故所有每行、每列有且仅有一个黑色格子的情况均有解。充分性证毕。

于是这道看起来十分复杂的题就变成了经典二分图傻*题,两个集合分别为行和列,黑色节点的行和列之间连边,问最小点覆盖是否为n。转化为最大匹配求即可。

这里为了效率跑了dinic。

#include <cstdio>
#include <cstring>
#include <queue>
#define N 500
#define M 200000
using namespace std;
queue<int> q;
int head[N] , to[M] , val[M] , next[M] , cnt , s , t , dis[N];
void add(int x , int y , int z)
{
	to[++cnt] = y , val[cnt] = z , next[cnt] = head[x] , head[x] = cnt;
	to[++cnt] = x , val[cnt] = 0 , next[cnt] = head[y] , head[y] = cnt;
}
bool bfs()
{
	int x , i;
	memset(dis , 0 , sizeof(dis));
	while(!q.empty()) q.pop();
	dis[s] = 1 , q.push(s);
	while(!q.empty())
	{
		x = q.front() , q.pop();
		for(i = head[x] ; i ; i = next[i])
		{
			if(val[i] && !dis[to[i]])
			{
				dis[to[i]] = dis[x] + 1;
				if(to[i] == t) return 1;
				q.push(to[i]);
			}
		}
	}
	return 0;
}
int dinic(int x , int low)
{
	if(x == t) return low;
	int temp = low , i , k;
	for(i = head[x] ; i ; i = next[i])
	{
		if(val[i] && dis[to[i]] == dis[x] + 1)
		{
			k = dinic(to[i] , min(temp , val[i]));
			if(!k) dis[to[i]] = 0;
			val[i] -= k , val[i ^ 1] += k;
			if(!(temp -= k)) break;
		}
	}
	return low - temp;
}
int main()
{
	int T;
	scanf("%d" , &T);
	while(T -- )
	{
		int n , i , j , x;
		scanf("%d" , &n) , s = 0 , t = 2 * n + 1;
		memset(head , 0 , sizeof(head)) , cnt = 1;
		for(i = 1 ; i <= n ; i ++ ) add(s , i , 1) , add(i + n , t , 1);
		for(i = 1 ; i <= n ; i ++ )
		{
			for(j = 1 ; j <= n ; j ++ )
			{
				scanf("%d" , &x);
				if(x) add(i , j + n , 1);
			}
		}
		while(bfs()) n -= dinic(s , 1 << 30);
		printf("%s\n" , n ? "No" : "Yes");
	}
	return 0;
}

 

 

posted @ 2017-07-03 13:57  GXZlegend  阅读(267)  评论(0编辑  收藏  举报