UVA10615 Rooks
题意:
给你一个 (n imes n) 的矩阵,把 (n) 行和 (n) 列视作 (2n) 个点。如果矩阵中坐标 ((i,j)) 的位置是 * ,就在第 (i) 行和第 (j) 列代表的点之间连一条无向边。因此你得到了一个二分图。
让你给每条边染色,要求每个点相邻的边中不能有重复的颜色。
求最小的染色数,并输出一种染色方案。
题解:
根据 Vizing 定理,二分图的最小染色数所有点的度数的最大值。
网上的大部分做法都是匹配做的,这里给出一种比较暴力的做法。
这种做法是在 OI Wiki 上了解到的,以下内容直接搬运自 OI Wki 。
从这里开始
按照顺序在二分图中加边。
我们在尝试加入边 ((x,y)) 的时候,我们尝试寻找对于 (x) 和 (y) 的编号最小的尚未被使用过的颜色,假设分别为 (lx) 和 (ly) 。
如果 (lx=ly) 此时我们可以直接将这条边的颜色设置为 (lx) 。
否则假设 (lx<ly) , 我们可以尝试将节点 (y) 连出去的颜色为 (lx) 的边的颜色修改为 (ly) 。
修改的过程可以被近似的看成是一条从 (y) 出发,依次经过颜色为 (lx,ly,lx,ly,cdots) 的边的有限唯一增广路。
因为增广路有限所以我们可以将增广路上所有的边反色,即原来颜色为 (lx) 的修改为 (ly) ,原来颜色为 (ly) 的修改为 (lx) 。
根据二分图的性质,节点 (x) 不可能为增广路节点,否则与最小未使用颜色为 (lx) 矛盾。
所以我们可以在增广之后直接将连接 (x) 和 (y) 的边的颜色设为 (lx) 。
总构造时间复杂度为 (O(nm)) 。
到这里结束
你问我既然有了为什么还要再写一篇题解?
因为原作者并没有给出具体的代码实现(至少 OI Wiki 上没有),我在网上也没有找到,所以就自己写了一下,希望能提供帮助。
ps:而且我的代码跑的飞快,在 UVA 上 rank14 欸。
AC 代码:
#include<bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
#define LLINF 0x3f3f3f3f3f3f3f3f
#define pii pair<int,int>
#define vi vector<int>
#define SZ(x) (int)x.size()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
using namespace std;
const int MAXN = 205;
const int MAXM = 1e4 + 5;
char g[MAXN][MAXN]; // 原图
struct edge { // 前向星
int to, next, w;
} e[MAXM * 2];
int cnt = 1;
int head[MAXN];
void add_edge(int u, int v, int w = 0) {
e[++cnt].to = v;
e[cnt].next = head[u];
e[cnt].w = w;
head[u] = cnt;
}
int col[MAXN][MAXN]; // col[u][c]表示点u的颜色为c的边的id
void change(int u, int L1, int L2) {
if(!col[u][L1]) { //无法增广
col[u][L2] = 0;
return;
}
int i = col[u][L1];
int v = e[i].to;
change(v, L2, L1); // 继续增广
e[i].w = e[i ^ 1].w = L2; // 修改颜色L1->L2
col[u][L2] = i;
col[v][L2] = i ^ 1;
}
void paint(int n) {
for(int u = 1; u <= n; u++) {
for(int i = head[u]; i; i = e[i].next) {
if(e[i].w > 0) // 已染色
continue;
int v = e[i].to;
int Lx = INF, Ly = INF;
for(int i = 1; i <= n; i++) { // 找最小的未使用颜色
if(!col[u][i])
Lx = min(Lx, i);
if(!col[v][i])
Ly = min(Ly, i);
}
if(Lx < Ly)
change(v, Lx, Ly);
if(Lx > Ly)
change(u, Ly, Lx);
int L = min(Lx, Ly);
e[i].w = e[i ^ 1].w = L; // 染色
col[u][L] = i;
col[v][L] = i ^ 1;
}
}
}
int deg[MAXN];
int ans[MAXN][MAXN];
void init(int n) {
cnt = 1; // 前向星从2开始存!
for(int i = 0; i < n + 5; i++) {
head[i] = deg[i] = 0;
for(int j = 0; j < n + 5; j++)
col[i][j] = ans[i][j] = 0;
}
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
int n;
scanf("%d", &n);
init(n * 2);
for(int i = 0; i < n; i++) {
getchar();
scanf("%s", g[i]);
}
for(int i = 0; i < n; i++) // 建图
for(int j = 0; j < n; j++)
if(g[i][j] == '*') {
add_edge(i + 1, j + 1 + n);
add_edge(j + 1 + n, i + 1);
deg[i + 1]++, deg[j + 1 + n]++;
}
paint(n);
for(int u = 1; u <= n; u++) // 更新答案
for(int i = head[u]; i; i = e[i].next)
ans[u][e[i].to - n] = e[i].w;
int num = 0;
for(int i = 1; i <= 2 * n; i++) // 查找最大度数
num = max(num, deg[i]);
printf("%d
", num);
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++)
printf("%d ", ans[i][j]);
printf("
");
}
}
}