P1129 [ZJOI2007] 矩阵游戏 题解

后面CSDN的做题记录就不再更新了(复制起来挺麻烦的)
以题解的形式发到CSDN里,cnblog里照常更新

P1129 [ZJOI2007] 矩阵游戏

题面:

题目描述

小 Q 是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏――矩阵游戏。矩阵游戏在一个 n × n n \times n n×n 黑白方阵进行(如同国际象棋一般,只是颜色是随意的)。每次可以对该矩阵进行两种操作:

  • 行交换操作:选择矩阵的任意两行,交换这两行(即交换对应格子的颜色)。
  • 列交换操作:选择矩阵的任意两列,交换这两列(即交换对应格子的颜色)。

游戏的目标,即通过若干次操作,使得方阵的主对角线(左上角到右下角的连线)上的格子均为黑色。

对于某些关卡,小 Q 百思不得其解,以致他开始怀疑这些关卡是不是根本就是无解的!于是小 Q 决定写一个程序来判断这些关卡是否有解。

输入格式

本题单测试点内有多组数据

第一行包含一个整数 T T T,表示数据的组数,对于每组数据,输入格式如下:

第一行为一个整数,代表方阵的大小 n n n
接下来 n n n 行,每行 n n n 个非零即一的整数,代表该方阵。其中 0 0 0 表示白色, 1 1 1 表示黑色。

输出格式

对于每组数据,输出一行一个字符串,若关卡有解则输出 Yes,否则输出 No

样例 #1
样例输入 #1
2
2
0 0
0 1
3
0 0 1
0 1 0
1 0 0
样例输出 #1
No
Yes
提示
数据规模与约定
  • 对于 20 % 20\% 20% 的数据,保证 n ≤ 7 n \leq 7 n7
  • 对于 50 % 50\% 50% 的数据,保证 n ≤ 50 n \leq 50 n50
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 200 1 \leq n \leq 200 1n200 1 ≤ T ≤ 20 1 \leq T \leq 20 1T20

图匹配最难三连问:这道题在考匹配?如何匹配?图怎么建?

这道题很神奇的在考二分图匹配。

为什么?

我们将矩阵看做 i : ( 1 ∼ n ) ⇝ j : ( n + 1 ∼ n + n ) i:(1 \sim n) \leadsto j:(n + 1 \sim n + n) i:(1n)j:(n+1n+n) 连边的邻接矩阵

其中 1 1 1 表示有连边, 0 0 0 表示未连边

那么,这交换两行/两列操作在干吗?

假设我们有这样的一个二分图
img

我们将这两列连边情况交换: a l e f t : [ 1 ∼ n ] ⇄ b l e f t : [ 1 ∼ n ] a_{left}:[1\sim n] \rightleftarrows b_{left}:[1\sim n] aleft:[1n]bleft:[1n]

其实就是交换 a l e f t 、 b l e f t a_{left}、b_{left} aleftbleft,(以交换 1 , 3 1,3 1,3 举例)如图
img

交换行也是一样的,即: a r i g h t ⇄ b r i g h t a_{right} \rightleftarrows b_{right} arightbright

我们要让主对角线全为 1 1 1

也就是
1 ⇝ n + 1 2 ⇝ n + 2 ⋮ n ⇝ n + n \begin{align*} 1 \leadsto n + 1 \\ 2 \leadsto n + 2 \\ \vdots \\ n \leadsto n + n \\ \end{align*} 1n+12n+2nn+n

如图:
img

(? 代表我们不需要知道是否有连边)

也就是对应的左右边两点有连边,即:
img

这个时候就可以使用二分图完美匹配

也就是二分图最大匹配 + 二分图每个点都被匹配

因为这个是 n × n n \times n n×n 的矩阵,所以二分图中的左右两边的点肯定相等

(tips:非偶数个点的二分图无法完美匹配,必定有一个点无法匹配)

跑一个二分图匹配,判断是否有 n n n 条边匹配就大功告成了

AC-code:

#include<bits/stdc++.h>
using namespace std;
		
int rd() {
	int x = 0, w = 1;
	char ch = 0;
	while (ch < '0' || ch > '9') {
		if (ch == '-') w = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + (ch - '0');
		ch = getchar();
	}
	return x * w;
}

void wt(int x) {
	static int sta[35];
	int f = 1;
	if(x < 0) f = -1,x *= f;
	int top = 0;
	do {
		sta[top++] = x % 10, x /= 10;
	} while (x);
	if(f == -1) putchar('-');
	while (top) putchar(sta[--top] + 48);
}
const int N = 205;
int match[N * N],n,vis[N * N];
int head[N * 2],nxt[N * N * 2],to[N * N * 2],cnt;
void add(int u,int v) {
	nxt[cnt] = head[u];
	to[cnt] = v;
	head[u] = cnt++;
}
void solve() {
	memset(head,-1,sizeof(head));
	cnt = 0;
	n = rd();
	for(int i = 1;i<=n;i++)
		for(int j = 1;j<=n;j++) 
			if(rd()) 
				add(i,j + n),add(j + n,i);
	int ans = 0;
	memset(match,0,sizeof(match));
	memset(vis,0,sizeof(vis));
	auto dfs = [&](auto self,int x,int tag) -> bool{
		for(int i = head[x];~i;i = nxt[i]) {
			int y = to[i];
			if(vis[y] == tag) continue;
			vis[y] = tag;
			if(!match[y] || self(self,match[y],tag)) {
				match[y] = x;
				return true;
			}
		}
		return false;
	};
	for(int i = 1;i<=n;i++) ans += dfs(dfs,i,i);
	if(ans == n) puts("Yes");
	else puts("No");
}

signed main() {
	
	int T = rd();
	while(T--) solve();
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值