[SNOI2024] 拉丁方

神秘二分图。

首先考虑 R=n 该怎么做,可以想到二分图。令每一行为左部点,数字为右部点,行向可以填的数字两边,填数相当于将这个二分图划分为 nC 组匹配。由于这是正则二分图,一定有解。

对于 R<n 的情况,先填左下角的部分。同样的建图方式,如果有一个右部点的度数 >nR,此时要划分的是 nR 组左部点的完美匹配,容易想到合法的必要条件为右部点的度数不大于 nR,充分性根据 Hall 定理显然(也可以建虚点补全为正则二分图)。

于是问题转化为划分,由于这题的二分图都是正则二分图,可以通过随机找增广路实现期望 Θ(n2logn) 的算法。对于更一般的二分图,我们要做的实际上是给边染色。考虑一条边一条边的染,假设当前在染 (u,v)u,v 的出边当前染过的颜色集合为 Su,Sv,则:

c1=mex(Su),c2=mex(Sv),若 c1=c2 则直接染成 c1,否则强制染成 c1 找到 v 染成 c1 的边 (v,w),将其更改为 c2,此时 w 的出边又有可能冲突,于是再将另一条冲突的边更改为 c1……不断循环下去(实际上是类似一个匈牙利的过程?),最后一定能找到一个点不冲突。

综上,复杂度 Θ(n3)Θ(n2logn)

#include <bits/stdc++.h>

using namespace std;

const int N = 1005;

int n, R, C, a[N][N];
bool vis[N];
int X[N * N], Y[N * N], c[N][N], m, d[N << 1];

inline int read() {
	register int s = 0, f = 1; register char ch = getchar();
	while (!isdigit(ch)) f = (ch == '-' ? -1 : 1), ch = getchar();
	while (isdigit(ch)) s = (s * 10) + (ch & 15), ch = getchar();
	return s * f; 
}

int main() {
	int T = read();
	while (T--) {
		n = read(); R = read(); C = read();
		memset(a, 0, sizeof a);
		for (int i = 1; i <= R; ++i)
			for (int j = 1; j <= C; ++j)
				a[i][j] = read();
		//solve C
		m = 0;
		for (int j = 1; j <= C; ++j) {
			for (int i = 1; i <= n; ++i) vis[i] = 1;
			for (int i = 1; i <= R; ++i) {
				vis[a[i][j]] = 0;
			}
			for (int i = 1; i <= n; ++i)
				if (vis[i]) {
					X[++m] = j; Y[m] = i + C;
				}
		}
		for (int i = 1; i <= C + n; ++i) d[i] = 0;
		for (int i = 1; i <= m; ++i) ++d[X[i]], ++d[Y[i]];
		bool flag = 1;
		for (int i = 1; i <= n; ++i) {
			if (d[C + i] > n - R) {
				flag = 0; break;
			}
		} if (!flag) { puts("No"); continue; }
		memset(c, 0, sizeof c);
		for (int i = 1; i <= m; ++i) {
			int c1 = 1, c2 = 1;
			int u = X[i], v = Y[i];
			while (c[u][c1]) ++c1;
			while (c[v][c2]) ++c2;
			c[u][c1] = v;
			c[v][c2] = u;
			if (c1 != c2) {
				int now = v, cc = c2;
				while (now) {
					swap(c[now][c1], c[now][c2]);
					now = c[now][cc];
					cc ^= c1 ^ c2;
				} 
			}
		}
		for (int j = 1; j <= C; ++j)
			for (int i = 1; i <= n - R; ++i)
				a[i + R][j] = c[j][i] - C;
		//solve R = n
		m = 0;
		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= n; ++j) vis[j] = 1;
			for (int j = 1; j <= C; ++j) vis[a[i][j]] = 0;
			for (int j = 1; j <= n; ++j)
				if (vis[j]) {
					X[++m] = i; Y[m] = j + n;
				}
		}
		for (int i = 1; i <= n + n; ++i) d[i] = 0;
		for (int i = 1; i <= m; ++i) ++d[X[i]], ++d[Y[i]];
		memset(c, 0, sizeof c);
		for (int i = 1; i <= m; ++i) {
			int c1 = 1, c2 = 1;
			int u = X[i], v = Y[i];
			while (c[u][c1]) ++c1;
			while (c[v][c2]) ++c2;
			c[u][c1] = v;
			c[v][c2] = u;
			if (c1 != c2) {
				int now = v, cc = c2;
				while (now) {
					swap(c[now][c1], c[now][c2]);
					now = c[now][cc];
					cc ^= c1 ^ c2;
				} 
			}
		}
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n - C; ++j)
				a[i][j + C] = c[i][j] - n;
		puts("Yes");
		for (int i = 1; i <= n; ++i) {
			for (int j = 1; j <= n; ++j) printf("%d ", a[i][j]);
			puts("");
		}
	} return 0;
}
posted @   Smallbasic  阅读(56)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App
点击右上角即可分享
微信分享提示