芝士:匈牙利&KM

背景

解决二分图最大匹配,以及完美匹配的最大权值

匈牙利

思路

考虑二分图上已经有了一些匹配,

然后现在我们将一个点加进去,如果这个点本身相连的点就有没有已经匹配的,那么就直接匹配上去,

如果所有的点都已经匹配,那么就让这些匹配的点试着改变他们所匹配的点

整体上类似于调整法

代码

#include<iostream>
#include<vector>
using namespace std;
int n,m,e;
vector<int> g[1005];
int mat[1005],vis[1005];//匹配的点
int ans=0;
bool dfs(int u,int fl)
{
    if(vis[u]==fl)
        return 0;
    vis[u]=fl;
    for(int i=0;i<g[u].size();i++)
    {
        int v=g[u][i];
        if(mat[v]==0||dfs(mat[v],fl))
        {
            mat[v]=u;
            return 1;
        }
    }
    return 0;
}
int main()
{
    cin>>n>>m>>e;
    for(int i=1,u,v;i<=e;i++)
    {
        cin>>u>>v;
        g[u].push_back(v);
    }
    for(int i=1;i<=n;i++)
        if(dfs(i,i))
            ans++;
    cout<<ans;
    return 0;
}

KM

思路

考虑也是用调整法去构造出一个解

我们将点赋上点权,\(x\)部分的点的点权初始为他们所连出去的边的最大的权值,\(y\)部分的点为0,设\(x\)部分的点的点权为\(lx_i\)\(y\)部分的点的点权为\(ly_i\)

显然,\(ans\le \sum_{i=1}^nlx_i+ly_i\)

接着就开始调整,但必须满足不管在什么时候都有\(lx[e[i].u]+ly[e[i].v]\ge e[i].w\)

如果我们将\(lx[e[i].u]+ly[e[i].v]==e[i].w\)的边拿出来,将这些边称作可行边,

那么这些可行边必然后构成一个图,显然这个图也是二分图,我们称其为可行图(实际上是笔者自己的定义),接着我们所需要的做的就是不停的向图上加边,使得这个二分图上匹配的点对数量为\(n\)

下文所提到的\(x\)部分和\(y\)部分均为可行边所构成的二分图上的两个部分

因为要保证没有边被删去,所以我们只能对\(x\)\(y\)整体进行操作,且进行操作的值互为相反数,比如\(x\)整体减去\(d\),那么\(y\)整体就必须加上\(d\),显然,这样的操作不会使边丢失,同时会使一些边加进来,

那么现在有两个问题:

1.可行图不一定联通,我们对哪一个可行子图进行操作(孤点也算可行子图)

2.操作的\(d\)是多少?

因为操作的顺序并不影响最终的结果,所以我们只选择\(x\)部比\(y\)部多\(1\)个的进行操作,应该很容易就可以证明我们一定存在这样的可行子图,

再者,因为最终的答案\(ans=\sum_{i=1}^{n}lx_i+ly_i\),而我们每一次选择必定是让答案减小,所以我们需要选择减少代价最少的一条边加入图中,

这实际上是变相的调整法,可以说明,这样调整之后的结果一定是最优的

调整的过程用匈牙利实现即可

时间复杂度即为\(O(n^4)\)

我们考虑整个过程就只加了一条边进行,所以只需要对这条边进行更新即可,不需要每一次都暴力跑匈牙利,时间复杂度就优化成为了\(O(n^3)\)

代码

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<vector>
using namespace std;
struct node
{
    int e;
    long long w;
};
int n,m;
int mat[505];//右边的匹配到左边的
vector<node> g[505];
int pre[505];
long long lx[505],ly[505];
long long sla[505];//针对右边
bool vis[505];
void bfs(int st)
{
    memset(sla,0x3f,sizeof(sla));
    memset(vis,0,sizeof(vis));
    memset(pre,0,sizeof(pre));
    int now,la=0,_ind=0;
    long long minn;
    mat[0]=st;
    int tot=0;
    while(1)
    {
        tot++;
        now=mat[la];
        minn=(1ll<<60);
        vis[la]=1;
        for(int i=0;i<g[now].size();i++)
        {
            int v=g[now][i].e;
            if(vis[v])
                continue;
            if(sla[v]>lx[now]+ly[v]-g[now][i].w)
            {
                sla[v]=lx[now]+ly[v]-g[now][i].w;
                pre[v]=la;
            }
        }
        for(int i=1;i<=n;i++)
            if(sla[i]<minn&&vis[i]==0)
            {
                minn=sla[i];
                _ind=i;
            }
        for(int i=0;i<=n;i++)
        {
            if(vis[i])
            {
                lx[mat[i]]-=minn;
                ly[i]+=minn;
            }
            else
                sla[i]-=minn;
        }
        la=_ind;
        if(mat[la]==-1)
			break;
	}
    while(la)
    {
        mat[la]=mat[pre[la]];
        la=pre[la];
    }
}
long long km()
{
    memset(mat,-1,sizeof(mat));
    for(int i=1;i<=n;i++)
        bfs(i);
    long long ret=0;
    for(int i=1;i<=n;i++)
        ret=ret+lx[i]+ly[i];
    return ret;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1,u,v,w;i<=m;i++)
    {
        cin>>u>>v>>w;
        g[u].push_back((node){v,w});
        //map[u][v]=w;
    }
    cout<<km()<<'\n';
    for(int i=1;i<=n;i++)
        cout<<mat[i]<<' ';
    cout<<'\n';
    return 0;
}
posted @ 2021-02-04 18:41  loney_s  阅读(181)  评论(0)    收藏  举报