UOJ171. 挑战NPC【WC2016】
一般图最大匹配
带花树
没想到建图居然建一般图,考场上想了很久的二分图最大匹配,大样例都过不了 (๑•́ ₃•̀๑)
现有 n 个小球,
m 个框子, e 个条件,条件(u,v) 表示 u 个小球能放进第v 个框子里,当框子里的小球数量 ≤ 1个时,我们称这个框子为半满,求在所有小球都在框子里时,半满的框子最多
前面的暴力和构造就不说了,考试的时候也没注意几个细节,大暴搜的地方写错了,WA爽了
最后几组
N
和
我们把框分拆成三个点,当没有小球放进去时(即没有左边的小球),这个图的最大匹配是1,当放进去一个小球时,最大匹配数是2。
当两个小球放进去时,最大匹配数还是2
那么根据这两个图我们发现,只需要拆点,框的三个点之间分别连边,小球再与框连边,求一个一般图(特别注意这不是二分图【看二分图定义】)最大匹配数
maxMatch
,答案
ANS=maxMatch−N
怎么求一般二分图最大匹配,我们用到带花树这种算法(算法详解请看博客最上面的链接,那个图确实比较犀利,不仔细看还真不怎么懂),为了大家明白这个算法,就在代码里多加一点注释就行
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <queue>
using namespace std;
const int maxn = 300 + 13, maxm = 100 + 13, maxE = maxn * maxn * 3;
int n, m, e, all;
int pos, head[maxE];
struct Edge {
int v, last;
}line[maxE];
queue<int>Q;
const int maxN = maxn * 3;
int link[maxN], inQ[maxN], base[maxN], used[maxN], blossom[maxN], fa[maxN];
//把框子拆点之后的编号
int getOrd(int k, int x) {return n + (k - 1) * 3 + x;}
//链表建边
void my_read(int a, int b) {
line[++pos] = (Edge) {b, head[a]};
head[a] = pos;
}
//多组数据的初始化
void init() {
pos = 0;
memset(head, 0, sizeof(head));
memset(link, 0, sizeof(link));
scanf("%d%d%d", &n, &m, &e);
for(int i = 1; i <= m; i++) {
//拆点
int x = getOrd(i, 1), y = getOrd(i, 2), z = getOrd(i, 3);
my_read(x, y); my_read(y, x);
my_read(y, z); my_read(z, y);
my_read(z, x); my_read(x, z);
}
for(int i = 1; i <= e; i++) {
int u, v;
scanf("%d%d", &u, &v);
for(int k = 1; k <= 3; k++) my_read(u, getOrd(v, k)), my_read(getOrd(v, k), u);
}
}
//找到一条增广路之后的增广
void change(int finish) {
for(int z = finish; z;) {
int y = fa[z], x = link[y];
link[z] = y; link[y] = z;
z = x;
}
}
//缩花
void contract(int x, int y) {
memset(used, 0, sizeof(used));
memset(blossom, 0, sizeof(blossom));
int LCA = 0;
#define pre fa[link[i]]
//暴力找LCA
for(int i = x; i; i = pre) {i = base[i]; used[i] = 1;}
for(int i = y; i; i = pre) {i = base[i]; if(used[i]) {LCA = i; break;}}
//给每个点打标机,以免重复
for(int i = x; base[i] != LCA; i = pre) {
if(base[pre] != LCA) fa[pre] = link[i];
blossom[base[i]] = 1;
blossom[base[link[i]]] = 1;
}
for(int i = y; base[i] != LCA; i = pre) {
if(base[pre] != LCA) fa[pre] = link[i];
blossom[base[i]] = 1;
blossom[base[link[i]]] = 1;
}
#undef pre
//头尾两个值要特殊处理
if(base[x] != LCA) fa[x] = y;
if(base[y] != LCA) fa[y] = x;
//换上每个点加入队列
for(int i = 1; i <= all; i++) {
if(blossom[base[i]]) {
base[i] = LCA;
if(!inQ[i]) {Q.push(i); inQ[i] = 1;}
}
}
}
void BFS(int st) {
memset(inQ, 0, sizeof(inQ));
memset(fa, 0, sizeof(fa));
for(int i = 1; i <= all; i++) base[i] = i;
while(!Q.empty()) Q.pop();
Q.push(st);
inQ[st] = 1;
while(!Q.empty()) {
int u = Q.front(); Q.pop();
for(int i = head[u]; i; i = line[i].last) {
//扩展点
int v = line[i].v;
if(base[u] == base[v] || v == link[u]) continue;
//如果形成奇环(找到跟 或者 这个点有匹配点且匹配点有父亲(表明进入过队列))
if(v == st || (link[v] && fa[link[v]])) contract(u, v);
else if(!fa[v]) {
fa[v] = u;
if(link[v]) {
Q.push(link[v]);
inQ[link[v]] = 1;
}
else {
//找到增广路了
change(v);
return;
}
}
}
inQ[u] = 0;
}
}
void solve() {
//all = 总点数
all = n + m * 3;
for(int i = 1; i <= all; i++) if(!link[i]) BFS(i);
int sum = 0;
for(int i = 1; i <= all; i++) if(link[i]) sum++;
sum /= 2;
printf("%d\n", sum - n);
for(int i = 1; i <= n; i++) printf("%d ", (link[i] - n - 1) / 3 + 1); printf("\n");
}
int main() {
freopen("npc2.in", "r", stdin);
int T;
scanf("%d", &T);
for(int i = 1; i <= T; i++) {
init();
solve();
}
return 0;
}