稳定婚配问题

一、稳定婚配问题

通过问题的名字可以看出这是一个类似匹配的问题,有男女各n人,每个人对其他人都有好感度,问如何匹配可以使每个人都能找到自己心仪的对象?

很显然,二分图匈牙利算法即可,这里不过多叙述。

现在加上稳定两个字,

即当前假设1号男生的对象是1号女生,2号男生的对象是2号女生,但如果1号男生对2号女生的好感度大于对1号女生的好感度,并且二号女生对1号男生的好感度也大于对2号男生的好感度,那么很不幸:

1号男生会和2号女生在一起了......(很现实的问题o(╥﹏╥)o

那么如果让你对这n个男生和n个女生进行婚配,你该如何进行匹配?

二、Gale-Shapley算法

  这里介绍GS算法来解决这个稳定婚配问题

1、首先先对每一个男生按照当前男生对n个女生的好感度从大到小尝试匹配一下,如果当前的要匹配的女生还没有对象,那么就匹配成功,否则就比较一下这个女生现有的对象和当前这个男生,这个女生对谁的好感度高,来选择和哪一个男生进行匹配,然后匹配失败的男生(失去对象的男生o(╥﹏╥)o)压入一个栈中。

2、遍历完一遍男生后,这时栈里有可能有元素,也有可能没有(没有即匹配结束),如果有,就按照第一步再来给这个男生匹配,当然尝试匹配的女生就是之前按好感度从大到小排的匹配失败的女生的下一个。

3、当栈中为空时,算法结束,匹配成功。

这个算法其实保证了男生匹配的女生一定是最佳的,因为更好的肯定是已经匹配失败了,而对于女生来说,只有可能找到最差的对象。

附上我的GS算法的模板

#include<bits/stdc++.h>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=1e4+10;
int man[N],gir[N];  //编号为i的男生/女生匹配的是哪一个女生/男生
int manpos[N];   //编号为i的男生当前匹配到的是他心中的第几位的女生
int manarr[N][N],girarr[N][N];  //编号为i的男生心中排在第j的女生的编号是什么
int n;
int manp(int manI,int girI)   //返回编号为girI的女生对编号为manI的男生的好感度排在第几
{
    for(int i=1;i<=n;i++)
       if(girarr[girI][i]==manI)
            return i;
       return -1;
}
stack<int>s;
void init()  //初始化
{
    met(man,0);
    met(gir,0);
    while(!s.empty())
        s.pop();
}
void Find(int mani)
{
    int nowgir=manarr[mani][manpos[mani]];
    if(gir[nowgir]==0){     //如果当前女生还没有对象,直接匹配
        man[mani]=nowgir;
        gir[nowgir]=mani;
    }
    else {
        int nowman=manp(mani,nowgir);   //现在要匹配的男生的好感度排名
        int oldman=manp(gir[nowgir],nowgir);//现有的对象的好感度的排名
        if(nowman<oldman){  //当前要匹配的男生更好
            manpos[gir[nowgir]]++;
            s.push(gir[nowgir]);
            man[mani]=nowgir;
            gir[nowgir]=mani;
        }
        else {  //现有的更好,不交换
            manpos[mani]++;
            s.push(mani);
        }
    }
}
int main()
{
    while(~scanf("%d",&n)){
        init();
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&manarr[i][j]);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
            scanf("%d",&girarr[i][j]);
            for(int i=1;i<=n;i++)
                manpos[i]=1;
        for(int i=1;i<=n;i++)
            Find(i);       //先为每个男生匹配一遍
        while(!s.empty()){
            int p=s.top();
            s.pop();
            Find(p);
        }
        for(int i=1;i<=n;i++)
                cout<<man[i]<<endl;
        for(int i=1;i<=n;i++)
            printf("%d  %d ",i,man[i]);
        printf("\n");
    }
}

如果我之前讲的不是很清楚地话,这里引用一下程序员杂志上的一篇文章再来说一遍

 

什么是GS算法?每当有人问作者这样的问题时,他总会引用这个例子:假如你是一个媒人,有若干个单身男子登门求助,还有同样多的单身女子也前来征婚。如果你已经知道这些女孩儿在每个男孩儿心目中的排名,以及男孩儿们在每个女孩儿心中的排名,你应该怎样为他们牵线配对呢?

最好的配对方案当然是,每个人的另一半正好都是自己的“第一选择”。这虽然很完美,但绝大多数情况下都不可能实现。比方说,男1号最喜欢的是女1号,而女1号的最爱不是男1号,这两个人的最佳选择就不可能被同时满足。如果好几个男孩儿最喜欢的都是同一个女孩儿,这几个男孩儿的首选也不会同时得到满足。当这种最为理想的配对方案无法实现时,怎样的配对方案才能令人满意呢?

其实,找的对象太完美不见得是好事儿,和谐才是婚姻的关键。如果男1号和女1号各有各的对象,但男1号觉得,比起自己现在的,女1号更好一些;女1号也发现,在自己心目中,男1号的排名比现男友更靠前。这样一来,这两人就可能抛弃各自现在的对象——如果出现了这种情况,我们就说婚姻搭配是不稳定的。作为一个红娘,你深知,对象介绍得不好没关系,就怕婚姻关系不稳定。给客户牵线配对时,虽然不能让每个人都得到最满意的,但搭配必须得稳定。换句话说,对于每一个人,在他心目中比他当前伴侣更好的异性,都不会认为他也是一个更好的选择。现在,我们的问题是:稳定的婚姻搭配总是存在吗?应该怎样寻找?

为了便于分析,我们下面做一些约定。我们用字母A、B、C对男性进行编号,用数字1、2、3对女性进行编号。我们把所有男性从上到下列在左侧,括号里的数字表示每个人心目中对所有女性的排名;再把所有女性列在右侧,用括号里的字母表示她们对男性的偏好。图1所示的就是2男2女的一种情形,每个男的都更喜欢女1号,但女1号更喜欢男B,女2号更喜欢男A。若按A-1、B-2进行搭配,则男B和女1都更喜欢对方一些,这样的婚姻搭配就是不稳定的。但若换一种搭配方案(如图2),这样的搭配就是稳定的了


图1 一个不稳定的婚姻搭配图

è¿éåå¾çæè¿°

可能很多人会立即想到一种寻找稳定婚姻搭配的策略:不断修补当前搭配方案。如果两个人互相都觉得对方比自己当前的伴侣更好,就让这两个人成为一对,剩下被甩的那两个人组成一对。


图2 一个稳定的婚姻搭配

è¿éåå¾çæè¿°

如果还有想要私奔的男女对,就继续按照他们的愿望对换情侣,直到最终消除所有的不稳定组合。容易看出,应用这种“修补策略”所得到的最终结果一定满足稳定性,但这种策略的问题在于,它不一定存在“最终结果”。事实上,按照上述方法反复调整搭配方案,最终可能会陷入一个死循环,因此该策略甚至不能保证得出一个确定的方案来,如图3所示。


图3 应用“修补策略”可能会产生死循环

è¿éåå¾çæè¿°

Gale-Shapley算法

1962年,美国数学家David Gale和Lloyd Shapley发明了一种寻找稳定婚姻的策略。不管男女各有多少人,也不管他们的偏好如何,应用这种策略后总能得到一个稳定的搭配。换句话说,他们证明了稳定的婚姻搭配总是存在的。有趣的是,这种策略反映了现实生活中的很多真实情况。

在这种策略中,男孩儿将一轮一轮地去追求他中意的女子,女子可以选择接受或者拒绝他的追求者。第一轮,每个男孩儿都选择自己名单上排在首位的女孩儿,并向她表白。此时,一个女孩儿可能面对的情况有三种:没有人跟她表白,只有一个人跟她表白,有不止一个人跟她表白。在第一种情况下,这个女孩儿什么都不用做,只需要继续等待;在第二种情况下,接受那个人的表白,答应暂时和他做情侣;在第三种情况下,从所有追求者中选择自己最中意的那一位,答应和他暂时做情侣,并拒绝所有其他追求者。

第一轮结束后,有些男孩儿已经有女朋友了,有些男孩儿仍然是单身。在第二轮追女行动中,每个单身男孩儿都从所有还没拒绝过他的女孩儿中选出自己最中意的那一个,并向她表白,不管她现在是否是单身。和第一轮一样,女孩儿们需要从表白者中选择最中意的一位,拒绝其他追求者。注意,如果这个女孩儿已经有男朋友了,当她遇到了更好的追求者时,她必须拒绝掉现在的男友,投向新的追求者的怀抱。这样,一些单身男孩儿将会得到女友,那些已经有了女友的人也可能重新变成光棍。在以后的每一轮中,单身男孩儿继续追求列表中的下一个女孩儿,女孩儿则从包括现男友在内的所有追求者中选择最好的一个,并对其他人说不。这样一轮一轮地进行下去,直到某个时候所有人都不再单身,下一轮将不会有任何新的表白发生,整个过程自动结束。此时的婚姻搭配就一定是稳定的了。

这个策略会不会像之前的修补法一样,出现永远也无法终止的情况呢?不会。下面我们将说明,随着轮数的增加,总有一个时候所有人都能配对。由于在每一轮中,至少会有一个男孩儿向某个女孩儿告白,因此总的告白次数将随着轮数的增加而增加。倘若整个流程一直没有因所有人都配上对了而结束,最终必然会出现某个男孩儿追遍了所有女孩儿的情况。而一个女孩儿只要被人追过一次,以后就不可能再单身了。既然所有女孩儿都被这个男孩儿追过,就说明所有女孩儿现在都不是单身,也就是说此时所有人都已配对。


图4 应用上述策略,三轮之后将得出稳定的婚姻搭配

è¿éåå¾çæè¿°

接下来,我们还需要证明,这样得出的配对方案确实是稳定的。首先注意到,随着轮数的增加,一个男孩儿追求的对象总是越来越糟,而一个女孩儿的男友只可能变得越来越好。假设男A和女1各自有各自的对象,但比起现在的对象,男A更喜欢女1。因此,男A之前肯定已经跟女1表白过。既然女1最后没有跟男A在一起,说明女1拒绝了男A,也就是说她有了比男A更好的男孩儿。这就证明了,两个人虽然不是一对,但都觉得对方比自己现在的伴侣好,这样的情况绝不可能发生。

我们把用来解决某种问题的一个策略,或者说一个方案,或者说一个处理过程,或者说一系列操作规则,或者更贴切地说,一套计算方法,叫做“算法”。上面这个用来寻找稳定婚姻的策略就叫做“Gale-Shapley算法”,有些人也管它叫“延迟认可算法”。

Gale-Shapley算法的意义和应用

每个算法都有它的实际意义,能给我们带来很多启发。Gale-Shapley算法最大的意义就在于,作为一个为这些男女牵线的媒人,你并不需要亲自计算稳定婚姻匹配,甚至根本不需要了解每个人的偏好,只需要按照这个算法组织一个男女配对活动就可以了。你需要做的仅仅是把算法流程当作游戏规则告诉大家,游戏结束后会自动得到一个大家都满意的婚姻匹配。整个算法可以简单地描述为:每个人都去做自己想做的事情。对于男性来说,从最喜欢的女孩儿开始追起是顺理成章的事;对于女性来说,不断选择最好的男孩儿也正好符合她的利益。因此,大家会自动遵守游戏规则,不用担心有人虚报自己的偏好。

历史上,这样的“配对游戏”还真有过实际应用,并且更有意思的是,这个算法的应用居然比算法本身的提出还早10年。早在1952年,美国就开始用这种办法给医学院的学生安排工作,这被称之为“全国住院医师配对项目”。配对的基本流程就是,各医院从尚未拒绝这一职位的医学院学生中选出最佳人选并发送聘用通知,当学生收到来自各医院的聘用通知后,系统会根据他所填写的意愿表自动将其分配到意愿最高的职位,并拒绝掉其他的职位。如此反复,直到每个学生都分配到了工作。那时人们并不知道这样的流程可以保证工作分配的稳定性,只是凭直觉认为这是很合理的。直到10年之后,Gale和Shapley才系统地研究了这个流程,提出了稳定婚姻问题,并证明了这个算法的正确性。

这个算法还有很多有趣的性质。比如说,大家可能会想,这种男追女女拒男的方案对男性更有利还是对女性更有利呢?答案是,这种方案对男性更有利。事实上,稳定婚姻搭配往往不止一种,然而上述算法的结果可以保证,每一位男性得到的伴侣都是所有可能的稳定婚姻搭配方案中最理想的,同时每一位女性得到的伴侣都是所有可能的稳定婚姻搭配方案中最差的。受篇幅限制,我们略去证明的过程。

这个算法会有一些潜在的问题。刚才我们已经说了,对于每位女性来说,得到的结果都是所有可能的稳定搭配中最差的一种。此时,倘若有某位女性知道所有其他人的偏好列表,经过精心计算,她有可能发现,故意拒绝掉本不该拒绝的人(暂时保留一个较差的人在身边),或许有机会等来更好的结果。因而,在实际生活中应用这种算法,不得不考虑一些可能的欺诈与博弈。

这个算法还有一些局限。例如,它无法处理2n个人不分男女的稳定搭配问题。一个简单的应用场景便是宿舍分配问题:假设每个宿舍住两个人,已知2n个学生中每一个学生对其余2n-1个学生的偏好评价,如何寻找一个稳定的宿舍分配?此时,Gale-Shapley算法就不再有用武之地了。而事实上,宿舍分配问题中很可能根本就不存在稳定的搭配。例如,有A、B、C、D四个人,其中A把B排在第一,B把C排在第一,C把A排在第一,而且他们三人都把D排在最后。容易看出,此时一定不存在稳定的宿舍分配方案。倘若A、D同宿舍,B、C同宿舍,那么C会认为A是更好的室友(因为C把A排在了第一),同时A会认为C是更好的室友(因为他把D排在了最后)。同理,B、D同宿舍或者C、D同宿舍也都是不行的,因而稳定的宿舍分配是不存在的。此时,重新定义宿舍分配的优劣性便是一个更为基本的问题。

稳定婚姻问题还有很多其他的变种,有些问题甚至是NP完全问题,至今仍然没有(也不大可能有)一种有效的算法。在图论、算法和博弈论中,这都是非常有趣的话题。

作者顾森,网名Matrix67,北京大学中文系应用语言学专业学生,数学爱好者。2005年开办数学博客http://www.matrix67.com,至今已积累上千篇文章,已有上万人订阅
 

最后再给一个例题poj3487

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<map>
#define exp 1e-8
#define mian main
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ll long long
#define pb push_back
#define PI  acos(-1.0)
#define inf 0x3f3f3f3f
#define w(x) while(x--)
#define int_max 2147483647
#define lowbit(x) (x)&(-x)
#define gcd(a,b) __gcd(a,b)
#define pq(x)  priority_queue<x>
#define ull unsigned long long
#define sc(x) scanf("%d",&x)
#define scl(x) scanf("%lld",&x)
#define pl(a,n) next_permutation(a,a+n)
#define ios ios::sync_with_stdio(false)
#define met(a,x) memset((a),(x),sizeof((a)))
using namespace std;
const int N=50;
int man[N],gir[N];
int manpos[N];
int manarr[N][N],girarr[N][N];
stack<int>s;
int n;
int mm[N],gg[N];
int manp(int mani,int giri)
{
    for(int i=1;i<=n;i++)
        if(girarr[giri][i]==mani)
        return i;
    return -1;
}
void Find(int mani)
{
    int nowgir=manarr[mani][manpos[mani]];
    if(gir[nowgir]==0){
        gir[nowgir]=mani;
        man[mani]=nowgir;
    }
    else{
        int oldman=manp(gir[nowgir],nowgir);
        int nowman=manp(mani,nowgir);
        if(oldman<nowman){
            manpos[mani]++;
            s.push(mani);
        }
        else{
            manpos[gir[nowgir]]++;
            s.push(gir[nowgir]);
            gir[nowgir]=mani;
            man[mani]=nowgir;
        }
    }
}
void init()
{
    met(man,0);
    met(gir,0);
    while(!s.empty())
        s.pop();
}
map<char,int>m,g;
char t[N];
int main()
{
    int T;
    sc(T);
    while(T--){
            scanf("%d",&n);
            init();
        char c;
        for(int i=1;i<=n;i++){
            cin>>c;
           m[c]=i;
           mm[i]=c-'a'+1;
        }
        for(int i=1;i<=n;i++){
            cin>>c;
            g[c]=i;
            gg[i]=c-'a'+1;
        }
        for(int i=1;i<=n;i++){
           cin>>t;
        for(int j=1;j<=n;j++)
            manarr[m[t[0]]][j]=t[j+1]-'A'+1;
        }
        for(int i=1;i<=n;i++){
            cin>>t;
            for(int j=1;j<=n;j++)
                girarr[g[t[0]]][j]=t[j+1]-'a'+1;
        }
        for(int i=1;i<=40;i++)
            manpos[i]=1;
        for(int i=1;i<=n;i++)
            Find(i);
        while(!s.empty()){
            int p=s.top();
            s.pop();
            Find(p);
        }
        for(int i=1;i<=n;i++)
            printf("%c %c\n",mm[i]+'a'-1,man[i]+'A'-1);
            printf("\n");
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值