[SNOI2024] 拉丁方
神秘二分图。
首先考虑 该怎么做,可以想到二分图。令每一行为左部点,数字为右部点,行向可以填的数字两边,填数相当于将这个二分图划分为 组匹配。由于这是正则二分图,一定有解。
对于 的情况,先填左下角的部分。同样的建图方式,如果有一个右部点的度数 ,此时要划分的是 组左部点的完美匹配,容易想到合法的必要条件为右部点的度数不大于 ,充分性根据 Hall 定理显然(也可以建虚点补全为正则二分图)。
于是问题转化为划分,由于这题的二分图都是正则二分图,可以通过随机找增广路实现期望 的算法。对于更一般的二分图,我们要做的实际上是给边染色。考虑一条边一条边的染,假设当前在染 , 的出边当前染过的颜色集合为 ,则:
设 ,若 则直接染成 ,否则强制染成 找到 染成 的边 ,将其更改为 ,此时 的出边又有可能冲突,于是再将另一条冲突的边更改为 ……不断循环下去(实际上是类似一个匈牙利的过程?),最后一定能找到一个点不冲突。
综上,复杂度 或 。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App