【一般图匹配问题】【带花树算法】(模板题)URAL-1099 Work Scheduling

引言

本文主要适用于自我学习复习和理解使用。(能给我点个赞更好了TuT)

参考

FROM

简介

Blossom Algorithm (带花树算法)主要用于解决一般图最大匹配问题

从二分图匹配到一般图匹配

Q:一般图匹配和二分图匹配问题的差别是在于哪里呢?
A:一般图可能存在奇环。

解释,奇环:即从A点到B点存在一条偶数长度的路径,也存在一条奇数长度的路径,两条路径结合构成一个和为奇数的路径也是环

我们可以通过二分图染色问题去考虑,

在二分图染色问题中奇路径的两个点的颜色一定不同,偶路径的两个点的颜色一定相同。
那如果存在奇环呢?
在这里插入图片描述
显然不行呀!最右点:我是什么颜色???

综上,我一般图匹配不能跟你二分图匹配一般见识,所以也就不能直接通过增广路寻找惹。

那好,我们不如直接假设接下来我们解决的图中是存在奇环的。

奇环就像上图所示,但点数可能会比上图的多,故

我们不妨设

某图中的某奇环点数为 2k+12k + 1 (奇数)

那我们就要对奇环部分特殊处理了,点数为 2k+12k+1很明显能够成功匹配的点有kk个,还会剩下1个点无法匹配,这个点所谓的无法匹配也仅仅是无法在这个奇环内进行匹配,但是它却可以拥有向外连边进行匹配的权利。

  1. 下面考虑一般图的增广算法。

从二分图的角度出发,每次枚举一个未匹配点(也就是上面 2k+12k+1 中的 11),搜索开始先设出发点为根并压入队列中,标记为 “o” ,接下来交错标记 “o” 和 “i” ,不难发现 “i” 到 “o” 这段边是匹配边。

(拿个无敌小的图帮助下理解,是不是如上面所说一致)

在这里插入图片描述

  1. 假设当前点是vv ,相邻点为 uu

case 1: 未拜访过uu,当 uu未匹配点,则找到增广路径,否则从 uu 的配偶找增广路。

case 2: uu 已拜访过,遇到标记 “o” 代表需要 缩花 ,否则代表遇到偶环,跳过。

遇到偶环的情况,将他视为二分图解决,故可忽略。 缩花 后,再新图中继续找增广路。

(还是可以通过上图进行理解)(如果不太好理解的话可以去看看见OI-wiki的图)

算法复杂度

在这里插入图片描述

例题

链接

URAL-1099 Work Scheduling

题意

警卫不敢一个人独自看守,于是计划成对看守

在这里插入图片描述
每行输入(i,j)(i, j) 表示 ii 警卫可以和 jj 警卫是可以一起进行工作的,寻找最佳匹配,为这些可以一起看守的警卫每一对提供一种制服,问需要多少种,匹配情况是什么样的。

题解

一般图最大匹配,看守我肯定是希望对数阅读越好,那样能看守的人肯定是最多的,所以就是最大匹配。

样例

输入

3
1 2
2 3
1 3

输出

2
1 2

需要制服的件数(注意哦一对是两件),匹配成功的情况是(1,2)

在这里插入图片描述

代码

const int MAXN = 250;
int N; // 点的个数
bool Graph[MAXN][MAXN]; //图
bool InQueue[MAXN],InPath[MAXN], InBlossom[MAXN]; //判断是否在队列内、是否访问过、是否开花过
int Start, Finish; //起始、结束
int Head, Tail; 
int NewBase;
int Match[MAXN]; //匹配
int Father[MAXN], Base[MAXN]; //用于lca的处理
int Count = 0; //匹配成功的数量
int Queue[MAXN]; //模拟队列,也可以直接用stl库内的queue直接进行实现

void CreateGraph(){
    int u, v; RST(Graph); //对图进行初始化
    RD(N); // 输入顶点个数
    //没有给出输入边的要求,就采用循环输入,直到输入文件结束
    while(scanf("%d%d", &u, &v) == 2){
        Graph[u][v] = Graph[v][u] = true; //u到v两点之间的双向边存在
    }
}
//队列的实现 弹出
/*
* push 压入队列
* Pop 从队列内
*/
void Push(int u){
    Queue[Tail] = u;
    Tail++;
    InQueue[u] = true; //表示u点已经进入队列
}
int Pop(){
    int res = Queue[Head];
    Head++;
    return res;
}
//寻找第一个匹配的顶点
int FindCommonAncestor(int u, int v){
    RST(InPath); //初始化是否访问过路,全部初始化为false,表示没有访问过
    while(true){
        u = Base[u];
        InPath[u] = true;
        if(u == Start) break;
        u = Father[Match[u]];
    }
    while(true){
        v = Base[v];
        if (InPath[v]) break; //如果v这个点的path是访问过的就可以直接跳出
        v = Father[Match[v]];  //lca寻找公共祖先,与v匹配的点的最近公共祖先 father[match[v]]
    }
    return v;
}
//回跳
void ResetTrace(int u){
    int v;
    while(Base[u] != NewBase){
        v = Match[u];
        InBlossom[Base[u]] = InBlossom[Base[v]] = true;
        u = Father[v];
        if (Base[u] != NewBase) Father[u] = v;
    }
}
void BloosomContract(int u, int v){
    NewBase = FindCommonAncestor(u, v);
    memset(InBlossom, false, sizeof(InBlossom));
    ResetTrace(u);
    ResetTrace(v);
    if (Base[u] != NewBase) Father[u] = v;
    if (Base[v] != NewBase) Father[v] = u;
    for(int tu = 1; tu <= N; tu++){
        if (InBlossom[Base[tu]]){
            Base[tu] = NewBase;
            if (!InQueue[tu]) Push(tu);
        }
    }
}
//寻找增广路径
void FindAugmentingPath(){
    RST(InQueue);
    RST(Father);
    for(int i = 1; i <= N; i++){
        Base[i] = i;
    }
    Head = Tail = 1;
    Push(Start);
    Finish = 0;
    while(Head < Tail){
        int u = Pop();
        for(int v = 1; v <= N; v++){
            if (Graph[u][v] && (Base[u] != Base[v]) && (Match[u] != v)){
                if ((v == Start) || (Match[v] > 0 && Father[Match[v]] > 0)) BloosomContract(u, v);
                else if (Father[v] == 0){
                    Father[v] = u;
                    if (Match[v] > 0) Push(Match[v]);
                    else {
                        Finish = v;
                        return ;
                    }
                }
            }
        }
    }
}
void AugmentPath()
{
    int u,v,w;
    u = Finish;
    while(u > 0)
    {
        v = Father[u];
        w = Match[v];
        Match[v] = u;
        Match[u] = v;
        u = w;
    }
}
void Edmonds()
{
    memset(Match,0,sizeof(Match));
    for(int u = 1; u <= N; u++)
        if(Match[u] == 0)
        {
            Start = u;
            FindAugmentingPath();
            if(Finish > 0) AugmentPath();
        }
}
//输出匹配结果
void PrintMatch()
{
    Count = 0;
    for(int u = 1; u <= N;u++)
        if(Match[u] > 0)
            Count++;
    OT(Count);
    for(int u = 1; u <= N; u++)
        if(u < Match[u])
            printf("%d %d\n",u, Match[u]);
}

int main(){
    //cout << false << 0 << '\n';
    CreateGraph();//建图
    Edmonds();//Edmonds' algorithm 匹配
    PrintMatch();
}

综上

以上内容是一次性书写的,很多地方没有经过太仔细的考虑所以可能存在许多纰漏,会逐步进行完善和丰富的。

posted @ 2020-07-24 16:50  月光不染是非  阅读(265)  评论(0编辑  收藏  举报