题目大意
现在给定一幅
N
个点,
N≤1500
M≤104
解题思路
考虑一个联通块的情况。
由于是一个二分图,所以加入有一种方案可以将左边的点权变成0,右边的点权变成1或2,那么肯定是一种合法方案。一种很巧妙的赋权方案就是把右边的点两两配对,一个作为起点,一个作为终点,由于是联通块,肯定有一条路径是连接着两个点的。然后我们可以把经过的这条路径上的边交替赋值1和2,那么右边的这两个点的点权肯定是1和2,左边经过的点恰好都是一条权值1的边和一条权值为2的边经过,相当于点权不变。如果一条边被经过多次只需把权值累加起来就可以了,不会影响到点权的合法性。然后我们再考虑一下一些特殊情况:
1. 联通块只有两个点:显然无解。
2. 联通块右边只有一个点无法配对:排除第一种情况后左边肯定有大于1个点,那么可以交换一下左右的点,对其他联通块没有影响。
3. 联通块右边的点有奇数个:那么先尽量配对,对于多出的一个点,随便找一个之前配对过的点权为1的点来作为起点和终点,然后只要使之前配对过的点连除去的边权设为1就可以了。
我们发现这样的赋权方案肯定是最优的,不合法的只有有两个点的联通块的情况,所以不会忽略掉合法方案。那么只需要构造出一组合法解就可以了。
程序
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 2e4 + 5;
int tot, Last[MAXN], Next[MAXN * 2], Go[MAXN * 2], Bel[MAXN * 2];
int N1, N2, M, x, y, X[MAXN], Y[MAXN], Col[MAXN];
bool Vis[MAXN], Flag[MAXN];
void Link(int u, int v, int bel) {
Next[++ tot] = Last[u], Last[u] = tot, Go[tot] = v, Bel[tot] = bel;
}
void Dfs(int Now) {
Vis[Now] = 1;
if (Now <= N1) X[++ x] = Now; else Y[++ y] = Now;
for (int p = Last[Now]; p; p = Next[p]) {
int v = Go[p];
if (Vis[v]) continue;
Dfs(v);
}
}
bool Do(int Now, int to, int Ord) {
Flag[Now] = 1;
if (Now == to) return 1;
for (int p = Last[Now]; p; p = Next[p]) {
int v = Go[p];
if (!Flag[v] && Do(v, to, Ord ^ 1)) {
int b = Bel[p];
Col[b] = (Col[b] + Ord + 1) % 3;
return 1;
}
}
}
bool Solve() {
if (y == 1 && x == 1) {
printf("No\n");
return 0;
}
if (y == 1) {
for (int i = 1; i <= x; i ++) swap(X[i], Y[i]);
swap(x, y);
}
for (int i = 1; i <= y; i += 2) {
for (int j = 1; j <= x; j ++) Flag[X[j]] = 0;
for (int j = 1; j <= y; j ++) Flag[Y[j]] = 0;
if (!(y & 1) || (i != y)) Do(Y[i], Y[i + 1], 0); else
Do(Y[1], Y[i], 0);
}
return 1;
}
int main() {
freopen("D.in", "r", stdin), freopen("D.out", "w", stdout);
scanf("%d%d%d", &N1, &N2, &M);
for (int i = 1; i <= M; i ++) {
int u, v;
scanf("%d%d", &u, &v);
Link(u, v + N1, i), Link(v + N1, u, i);
}
for (int i = 1; i <= N1; i ++) {
if (!Vis[i]) {
x = y = 0;
Dfs(i);
if (!Solve()) return 0;
}
}
printf("Yes\n");
for (int i = 1; i <= M; i ++) printf("%d ", Col[i]);
}