UOJ171. 挑战NPC【WC2016】【带花树】【一般图匹配】

UOJ171. 挑战NPC【WC2016】

一般图最大匹配 带花树


题目飞机票
带花树(一般最大匹配)讲解


没想到建图居然建一般图,考场上想了很久的二分图最大匹配,大样例都过不了 (๑•́ ₃•̀๑)

现有 n 个小球,m个框子, e 个条件,条件(u,v)表示 u 个小球能放进第v个框子里,当框子里的小球数量 1个时,我们称这个框子为半满,求在所有小球都在框子里时,半满的框子最多

前面的暴力和构造就不说了,考试的时候也没注意几个细节,大暴搜的地方写错了,WA爽了

最后几组 N M达到100,数据也没有特殊性:
我们把框分拆成三个点,当没有小球放进去时(即没有左边的小球),这个图的最大匹配是1,当放进去一个小球时,最大匹配数是2。
这里写图片描述
当两个小球放进去时,最大匹配数还是2
这里写图片描述
那么根据这两个图我们发现,只需要拆点,框的三个点之间分别连边,小球再与框连边,求一个一般图(特别注意这不是二分图【看二分图定义】)最大匹配数 maxMatch ,答案 ANS=maxMatchN

怎么求一般二分图最大匹配,我们用到带花树这种算法(算法详解请看博客最上面的链接,那个图确实比较犀利,不仔细看还真不怎么懂),为了大家明白这个算法,就在代码里多加一点注释就行

#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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值