二分图匹配

二分图匹配

umm,本来是作业,介绍一个自己感兴趣的知识,于是写了篇画风诡异的,懒得改了,直接当我的二分图匹配的匈牙利算法的学习笔记吧(怠惰哦)(我完全不知道卡什是谁...)


さあ、王の話をするとしよう!
来讲一个王的故事吧.


降临

众所周知,情人节快到了.
什么?情人节已经过了?没关系,我们加一个定语.

众所周知,月球人的情人节快到了.

更加充分一点:

众所周知,生活在中国的月球人的情人节快到了.

在这段万众瞩目的时间里,恐怕会发生很多很有意思的事情吧.
但不幸的是,本该在这期间有所行动的人们很多还不知道该将目标指向谁.
而幸运的是,你出现了.你有一个大胆的设想:按照身边人相互的好感关系,将尽可能多对相互都有好感的男女(忽略其他情况)”匹配”在一起,造福大众!

等等,哪里不对吧.你如何准确的得知身边的人中,谁和谁互有好感?
再等等,为什么你要干这种事?为了自己附近方圆百米只有自己一条单身狗?”旺旺”?似乎解释不通.让我们来修改一下设定.

你是一个来拯救迷茫的人们的迷之造访者(喂,迷字用得多了点).你用卡什之眼(???)的力量得知了所需要的好感关系.你要做的还是一样,将尽可能多对相互都有好感的男女”匹配”在一起,造福大众,让他们铭记你,感激你,赞颂你.

你,将加冕为王.

然而

然而在你提出这个大胆的构想之后,你终于发现,你太天真了.虽然身边的人不多,只有 101 数量级,但是他们之间的关系最多可以达到 102 数量级.而这些关系之间还会互相影响,实在是太过复杂,对于年幼而不擅长数学的你,实在是有些强人所难了.但是之前你已经说过了,”包在我身上了”,看来是没办法了.终究还是不行.

转机

就在你几乎放弃的时候,你听说了一种魔术形式:程序.这是一种运行于被称为计算机的魔术介质上的一种东西.使用被正确配置的程序可以迅速完成需要的计算,即使需要考虑的情况复杂.正是智慧,力量,魔术的完美结合.你的眼中仿佛出现了高光.希望!看到希望了!终究,我还是要成为被称颂的王的人!

希望背后

当你找到了新世界大门的时候,难以抑制的兴奋感迎面而来.但是,你突然意识到:你没有新世界大门的钥匙.虽然整个方法是存在的,利用那种魔术可以解决这个问题,但你没能力完成它的前提:你没法绘制出正确配置的程序.看着新世界的大门,上面的锁孔,高光消失了.终究还是无能为力…吗?

指引

“即使这样说显得很自作多情,但我还是要说,那就是命运的邂逅.”
那时,你,和一个至今还不知从哪里来,到哪里去,为了什么而出现的,自称梅莉的人物,邂逅于门前.

“哦哦,这样啊,我了~解了.我问你,如果一定要你自己完成的话,你会怎么做呢?比如像这样~?”
梅莉边说边画出了一幅简单的关系图,问道.

二分图匹配的简单例子1

[解说:图上左边的点分别表示男一,男二,男三,男四,右边的是女一,女二,女三,女四(心疼女四半秒),线表示两人互有好感]

“嗯…这张图倒是简单.那么,先把男一和女一分配到一起吧.”
“正解!下面呢?”
“嗯,那么再把男二和女二配成一对吧!”
“嗯,嗯.”

二分图匹配的简单例子2

“下面的话,男三…啊,和男三有好感的都被配上对了!怎么办啊!”
“没关系啊,我们将之前的配对断键,试着给男三也配对啊”
“哦,那么我看看…女二和男二成键了…女二还可以和…诶?女一也被分配过了!”
“嗯~,那么,怎么办呢?我们手中似乎已经有了足够的工具解决这个问题了哦~”
“莫非,对于目标已经被分配了的男二,用同样的方法在进行一次吗?”
“完全正确!这就是所谓的递归啊!”
“再重新为男二查找,再为男一查找…啊,男一可以和女三配对,那么男二就可以和女一,男三就可以和女二了!”

二分图匹配的简单例子3

“果然被选中的人最棒了!”
“嗯!…嗯?”
“没,没什么.看,看男四怎么办!”
“男四…无论怎么调整都不能配上对啊…看来需要心疼的不只是女四啊…”
“嗯,没错!这样,这个问题就解决了哦~❤”
“喂,为什么用那种令人起疑的语气!”
“ひみつ.”

曙光&王座

似乎,知道了啊.
虽然很后悔没能在那个自称梅莉的人离开之前多和她说上几句话,但是至少找到了解决的方法.还真是要谢谢她.嗯…还是有些在意,有些细节…

于是,在掌握了解决问题的方法之后,你又经过了一段时间的研究,掌握了刻画能够在魔法介质上工作的魔术的方法.终于,能实现了.

你叫来了那些人,向他们展示了自己的成果,并解释了一些细节.

#include<cstdio>
#include<cstring>
using namespace std;
//每个人本有一个编号
//但是为了能够处理问题
//必须将男女分别从1到n(男生数),m(女生数)编号
//因此需要对照表
const int id[]={0,1,2,3,6,7,10,13,16,18,19,21,23,24,25,26,27,32,35,36,38,41,4,5,8,9,11,12,14,15,17,19,20,22,28,29,30,31,33,34,37,39,40};
const int MAX=1010<<1;
int n,m;    //意义见上面
int map[MAX][MAX];  //用于表示关系
int pi_bond[MAX];   //记录配对的人是谁
bool failed[MAX];
//一次寻找中如果某个人无法重新配对
//那么这次寻找中他将始终无法重新配对
//因此加以记录可以节省时间

bool find(int x)    //为男x配对.返回是否成功
{
    for(int i=n+1;i<=n+m;i++) if(map[x][i])   //尝试每个可能情况
    {
        if(!pi_bond[i])
        {
            pi_bond[i]=x;   //男x与女i配对成功
            return true;
        }
        if(!failed[i])  //可能能重新配对
        {
            if(pi_bond[i]!=x && find(pi_bond[i])){
                pi_bond[i]=x;   //重新配对成功了
                return true;
            }else{
                failed[i]=1;    //失败了
            }
        }
    }
    return false;   //完全不行啊
}

int main()
{
    int x,y,e;
    scanf("%d%d%d",&n,&m,&e);    //读入男女数和关系数
    for(int i=1;i<=e;i++)
    {
        scanf("%d%d",&x,&y);    //男x与女y互有好感
        if(y<=m) map[x][n+y]=1;  //记录
    }
    int ans=0;  //记录最大匹配数
    for(int i=1;i<=n;i++)   //为男1到男n尝试匹配
    {
        memset(failed,0,sizeof(failed));
        //上次不可能再次分配的这次不一定
        //因此要还原
        if(find(i)) ans++;  //如果成功了的话就累加
    }
    printf("\nsum=%d\n",ans); //告诉大家能匹配多少对
    for(int i=1;i<=m;i++)
    {
        if(pi_bond[n+i]) printf("%d with %d\n",id[n+i],id[pi_bond[n+i]]);
        else printf("%d single all the way.\n",id[n+i]);
        //对每对,女前男后的输出其原始编号
        //对于没成功匹配的,输出...
        //没成功匹配的男x,抱歉,不会提到你的
    }
    return 0;
}

接着,你拿出了你用卡什之眼的力量(???)得到的关系表(万万没想到这有这么点):

21 20 5
4 1
4 15
5 10
9 15
16 5

然后将表提供给了设置好的程序.不负众望,你得到了结果!

sum=4
4 with 6
5 single all the way.
8 single all the way.
9 single all the way.
11 with 27
12 single all the way.
14 single all the way.
15 single all the way.
17 single all the way.
19 with 7
20 single all the way.
22 single all the way.
28 single all the way.
29 single all the way.
30 with 18
31 single all the way.
33 single all the way.
34 single all the way.
37 single all the way.
39 single all the way.

人们在欢呼.仿佛划破黑暗的曙光.

而你,将加冕为王

卡什之眼

你曾加冕为王.

然而,你得到的是不完全的关系图.而且最致命的是,这甚至并非是严格的”互有好感”,而只是单方面的.(真是悲伤).
(不过你想想,卡什之眼什么的这种东西靠谱吗)

你离开了王座,和一位名叫akua的神见了面.

但你留下的处理这种问题的方法被人铭记,并称之为”匈牙利算法”.(真是可惜,连冠名的机会都没有).

人们仍不知道,你那天所见的,自称为梅莉的人,的真实身份.

完.


梅莉

“这就是王的故事.王是不幸的.他依赖了错误的力量(所以说卡什之眼究竟是个什么鬼啊),而且最严重的是,他错误的配置了那个魔术而自己还浑然不觉.”
“哦?是吗?究竟是哪里?”认真听着眼前的迷之少女讲述的王的故事的你并没有注意到问题出现在了哪里.
“哈,还没看出来吗?仔细想想,会不会出现一个元素被匹配多次的情况?嘿嘿,说多了就没意思了~”
“好像是这样诶.那么,其实应该怎么配置?”
“像这样就可以了哦”
少女随手摆出了这样的东西.

#include<cstdio>
#include<cstring>
using namespace std;
//每个人本有一个编号
//但是为了能够处理问题
//必须将男女分别从1到n(男生数),m(女生数)编号
//因此需要对照表
const int id[]={0,1,2,3,6,7,10,13,16,18,19,21,23,24,25,26,27,32,35,36,38,41,4,5,8,9,11,12,14,15,17,19,20,22,28,29,30,31,33,34,37,39,40};
const int MAX=1010<<1;
int n,m;    //意义见上面
int map[MAX][MAX];  //用于表示关系
int pi_bond[MAX];   //记录配对的人是谁
bool visited[MAX];  //在本次搜索中是否被访问过

bool find(int x)    //为男x配对.返回是否成功
{
    for(int i=n+1;i<=n+m;i++) if(map[x][i] && !visited[i])   //尝试每个可能情况
    {
        visited[i]=1;//标记已访问
        if(!pi_bond[i] || find(pi_bond[i]))//i未匹配或是可以重新匹配
        {
            pi_bond[i]=x;   //男x与女i配对成功
            return true;
        }
    }
    return false;   //完全不行啊
}

int main()
{
    int x,y,e;
    scanf("%d%d%d",&n,&m,&e);    //读入男女数和关系数
    for(int i=1;i<=e;i++)
    {
        scanf("%d%d",&x,&y);    //男x与女y互有好感
        if(y<=m) map[x][n+y]=1;  //记录
    }
    int ans=0;  //记录最大匹配数
    for(int i=1;i<=n;i++)   //为男1到男n尝试匹配
    {
        memset(visited,0,sizeof(visited));
        //上次不可能再次分配的这次不一定
        //因此要还原
        if(find(i)) ans++;  //如果成功了的话就累加
    }
    printf("\nsum=%d\n",ans); //告诉大家能匹配多少对
    for(int i=1;i<=m;i++)
    {
        if(pi_bond[n+i]) printf("%d with %d\n",id[n+i],id[pi_bond[n+i]]);
        else printf("%d single all the way.\n",id[n+i]);
        //对每对,女前男后的输出其原始编号
        //对于没成功匹配的,输出...
        //没成功匹配的男x,抱歉,不会提到你的
    }
    return 0;
}

“像这样稍加修改,避免在一次匹配中出现重复访问同一个元素的情况,就能得到正确的结果了.”
看着她毫不费力的展示如此复杂的东西,你被折服了.你情不自禁的问出:”我能否有幸耳闻您的大名?”

银铃般的笑声.
“你可以称呼我为梅莉哦~❤”

完….?


umm……脑洞起来停不下来了.就这么着吧.故事算是结束了,脑洞填完了.

蒟蒻而且文笔极差的我在此鞠躬.无论是谁出于什么原因,感谢你看到这里.希望这个故事还算有趣.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值