二分图染色
讲解
- 性质:给二分图的每条边染色,同色边无交点,最小颜色数为所有点度数最大值(\(\max\limits_{v} deg(v)\))。
- 构造:类似增广路给每条边依次染色。当前染色边 \((u,v)\)
- 如果\(u\)和\(v\)未连接的颜色集合有交集,直接染
- 否则,设 \(c1, c2\) 分别为 \(u, v\) 邻边未染颜色集合中一颜色,将 \((u,v)\) 染成 \(c1\),若 \(v\) 邻边中也有 \(c1\),则将该边 \(c1\) 变成 \(c2\),\(c2\) 又有可能会重复,重边则换成\(c1\),这样产生连锁反应直到不重为止,发现路径上边权 \(c1\),\(c2\) 进行翻转。
- 实现:左右分别记录 \(trans(u,c)\):表示 \(u\) 走颜色 \(c\) 边到达的点。染色时, \(pos\) 迭代右半点,vector 记录路径。
- code: 见下例题
[SNOI2024] 拉丁方
- 题意:给矩阵左上角 \(r*n\),补全 \(n*n\) 矩阵,使得行列都为 \(n\) 排列。
- 思路:需要填的分成两部分:\([1, r] * [c+1, n]\) (B性质则无);和 \([r+1, n] * [1,n]\)。
两部分都是给了若干行(列)能填的数字集合。二分图染色,以填的数字为左半点,行(列)为右端点,集合可以通过连边来限制,边的颜色为列(行)。这样同色边无交点,保证了一行数字互不相同(左半),同行同列只填一个数(右半)。封装一个函数,跑两次即可。
- code
戳我
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef vector<int> vec;
typedef pair<int, int> PII;
const int N = 3e3 + 5;
int A[N][N], tl[N][N], tr[N][N], dl[N], dr[N], id[N][N];
bool mark[N];
pair<int, vec> solve(int n1, int n2, vector<PII> G) {
memset(tl, 0, sizeof(tl)); memset(tr, 0, sizeof(tr)), memset(dl, 0, sizeof(dl)), memset(dr, 0, sizeof(dr));
int cc = 0; vec ans(G.size());
for(auto it: G) dl[it.fi]++, dr[it.se]++, id[it.fi][it.se] = ++cc;
int lim = max(*max_element(dl + 1, dl + n1 + 1), *max_element(dr + 1, dr + n2 + 1));
for(auto it : G) {
int u = it.fi, v = it.se, flg = 0;
for(int j = 1; j <= lim; j++) if(tl[u][j] + tr[v][j] == 0) flg = j;
if(flg) tl[u][flg] = v, tr[v][flg] = u;
else {
int flg1 = 0, flg2 = 0;
for(int j = 1; j <= lim; j++) if(!tl[u][j]) flg1 = j;
for(int j = 1; j <= lim; j++) if(!tr[v][j]) flg2 = j;
int pos = v;
vec cl, cr;
while(pos) {
cr.push_back(pos);
if(tr[pos][flg1]) {
cl.push_back(tr[pos][flg1]);
pos = tl[tr[pos][flg1]][flg2];
}
else break;
}
for(auto id: cl) swap(tl[id][flg1], tl[id][flg2]);
for(auto id: cr) swap(tr[id][flg1], tr[id][flg2]);
tl[u][flg1] = v, tr[v][flg1] = u;
}
}
for(int i = 1; i <= n1; i++) for(int j = 1; j <= lim; j++) if(tl[i][j]) {ans[id[i][tl[i][j]]-1] = j;} //color of edge
return {lim, ans};
}
int main() {
int T; scanf("%d", &T);
while(T--) {
int n, R, C; scanf("%d%d%d", &n, &R, &C);
for(int i = 1; i <= R; i++) for(int j = 1; j <= C; j++) scanf("%d", &A[i][j]);
vector<PII> G;
for(int i = 1; i <= R; i++) {
memset(mark, 0, sizeof(mark));
for(int j = 1; j <= C; j++) mark[A[i][j]] = 1;
for(int j = 1; j <= n; j++) if(!mark[j]) G.push_back({j, i});
}
auto it = solve(n, R, G);
if(it.fi != n - C) {puts("No"); continue;}
for(int i = 0; i < G.size(); i++) {A[G[i].se][C + it.se[i]] = G[i].fi;}
G.clear();
for(int j = 1; j <= n; j++) {
memset(mark, 0, sizeof(mark));
for(int i = 1; i <= R; i++) mark[A[i][j]] = 1;
for(int i = 1; i <= n; i++) if(!mark[i]) G.push_back({i, j});
}
it = solve(n, n, G);
for(int i = 0; i < G.size(); i++) A[R + it.se[i]][G[i].se] = G[i].fi;
puts("Yes");
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) printf("%d ", A[i][j]); puts("");
}
}
return 0;
}