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