二分图染色

讲解

  • 性质:给二分图的每条边染色,同色边无交点,最小颜色数为所有点度数最大值(\(\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;
}