[SNOI2024] 拉丁方

神秘二分图。

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

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

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

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

综上,复杂度 \(\Theta(n^3)\)\(\Theta(n^2\log n)\)

#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 @ 2024-02-05 11:17  Smallbasic  阅读(88)  评论(1)    收藏  举报