UOJ80 二分图最大权匹配

草,学了一下午假板子,sb博客害人

题目大意:

一个教室有\(n\)个男生和\(m\)个女生,某些男女之间愿意早恋(雾),其早恋好感度为\(w_i\),问怎样让男女配对使得班里好感度之和最大

\(n\le 400\)

二分图最优匹配模型,KM算法板子题

二分图中我们介绍了一些定义,包括最优匹配、完美匹配、顶标、相等子图

相等子图的完美匹配就是二分图的最优匹配

我们考虑先给每个点赋顶标,或者说对于好感度的期望值

首先每个男生的期望是她愿意早恋对象的好感度的最大值,女生是\(0\)(即每个男生都想要最喜欢的人,女生都很佛)

这样必定满足\(a_u+b_v\ge val(u,v)\)

然后试图在这个的相等子图中寻找完美匹配

不过大概率找不到

找不到怎么办呢?一般是出现了两个男生想要一个女生的情况,那么我们只能给某一个男生安排另外的女生,所以我们要降低已经匹配过男生的期望

当然为了不漏掉任何一个女生,我们不能降低的太多,应该降低到刚好刚才没被人选过的女生可以入选

也就是说我们对于每个刚才挑过人的男生,应该降低\(d=min\{a_u+b_v-val(u,v)\}\)其中\(vis_{boy}[u]=1,vis_{girl}[v]=0\)

但是男生们期望降低了,刚才匹配过的女生不能不要啊\(qwq\),所以应该将刚才每个匹配过的女生期望增加\(d\)保证刚才被匹配过的女生不会被丢出相等子图

这样每次会有新的女生加入相等子图,且不会有人退出

但是\(n\ne m\)的情况怎么办啊(雾)

那为了匹配顺利,我们只能添加好感度为\(0\)的假人了……

具体实现一些细节在代码里面:

\(dfs\)版本:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
namespace red{
#define int long long
#define eps (1e-8)
    inline int read()
    {
        int x=0;char ch,f=1;
        for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
        if(ch=='-') f=0,ch=getchar();
        while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
        return f?x:-x;
    }
    const int N=410,inf=0x3f3f3f3f;
    int n,m,k;
    int bl,br;
    int head[N],cnt;
    int love[N][N];
    int g[N];
    int f[N],ex_girl[N],ex_boy[N];
    int slack[N];
    bool vis_boy[N],vis_girl[N];
    inline bool find(int boy)//稍微魔改过的匈牙利
    {
        vis_boy[boy]=1;
        for(int girl=1;girl<=m;++girl)
        {
            if(vis_girl[girl]) continue;
            int gap=ex_boy[boy]+ex_girl[girl]-love[boy][girl];
            if(gap==0)//如果是相等子图,直接更新
            {
                vis_girl[girl]=1;
                if(!f[girl]||find(f[girl]))
                {
                    f[girl]=boy;
                    g[boy]=girl;
                    return 1;
                }
            }
            else//不是的话记录一下最少改变多少期望
            {
                slack[girl]=min(slack[girl],gap);
            }
        }
        return 0;
    }
    inline void km()
    {
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=n;++j)
            {
                ex_boy[i]=max(ex_boy[i],love[i][j]);//男生初始期望是所有好感度最大值
            }
        }
        for(int i=1;i<=n;++i)
        {
            memset(slack,inf,sizeof(slack));
            memset(vis_boy,0,sizeof(vis_boy));
            memset(vis_girl,0,sizeof(vis_girl));
            if(find(i)) continue;//如果直接匹配成功就可以跳了
            while("haku")//失败了,扩大相等子图范围
            {
                int d=inf,t;
                for(int j=1;j<=n;++j)
                    if(!vis_girl[j]) d=min(d,slack[j]);//找到女生没被匹配过里面需要降低的最小值
                for(int j=1;j<=n;++j)
                {
                    if(vis_boy[j]) ex_boy[j]-=d;//匹配过的男生降低d
                    if(vis_girl[j]) ex_girl[j]+=d;//匹配过的女生提高d
                    else//没被匹配过的女生
                    {
                        slack[j]-=d;
                        if(!slack[j]) t=j;//如果男生全部削减完d之后可以进入相等子图,做个标记
                    }
                }
                if(!f[t]) break;//如果进入的女生没有配对的男生,那么说明我们可以找到新的一对,变成完美匹配
                vis_girl[t]=1,vis_boy[f[t]]=1;//否则继续改变期望
                t=f[t];
                for(int j=1;j<=n;++j)
                    slack[j]=min(slack[j],ex_boy[t]+ex_girl[j]-love[t][j]);
            }//注意不要在里面多次匈牙利,复杂度会炸
            memset(vis_boy,0,sizeof(vis_boy));
            memset(vis_girl,0,sizeof(vis_girl));
            find(i);
        }
        int ret=0;
        for(int i=1;i<=bl;++i)
        {
            ret+=love[i][g[i]];
        }
        printf("%lld\n",ret);
        for(int i=1;i<=bl;++i) printf("%lld ",love[i][g[i]]?g[i]:0ll);
        puts("");
    }
    inline void main()
    {
        n=read(),m=read(),k=read();
        bl=n,br=m;
        n=max(n,m),m=n;//假人代打
        for(int x,y,w,i=1;i<=k;++i)
        {
            x=read(),y=read(),w=read();
            love[x][y]=w;
        }
        km();
    }
}
signed main()
{
    red::main();
return 0;
}

\(bfs\)版本鸽子了,以后有空再补吧

猜你喜欢

转载自www.cnblogs.com/knife-rose/p/12093110.html