Tour 二分图最大权匹配

In the kingdom of Henryy, there are N (2 <= N <= 200) cities, with M (M <= 30000) one-way roads connecting them. You are lucky enough to have a chance to have a tour in the kingdom. The route should be designed as: The route should contain one or more loops. (A loop is a route like: A->B->……->P->A.)
Every city should be just in one route.
A loop should have at least two cities. In one route, each city should be visited just once. (The only exception is that the first and the last city should be the same and this city is visited twice.)
The total distance the N roads you have chosen should be minimized.

Input
An integer T in the first line indicates the number of the test cases.
In each test case, the first line contains two integers N and M, indicating the number of the cities and the one-way roads. Then M lines followed, each line has three integers U, V and W (0 < W <= 10000), indicating that there is a road from U to V, with the distance of W.
It is guaranteed that at least one valid arrangement of the tour is existed.
A blank line is followed after each test case.

Output
For each test case, output a line with exactly one integer, which is the minimum total distance.

Sample Input

1
6 9
1 2 5
2 3 5
3 1 10
3 4 12
4 1 8
4 6 11
5 4 7
5 6 9
6 5 4

Sample Output

42

二分图最大权匹配基础上求二分图最小权匹配的模板题,应用 Kuhn-Munkre 算法。
下面说明一下这个算法:
这个算法的核心思想是贪心,和MST的求解过程在思路上是相似的,都是先选择当前最优的情况并入边集,之后判断并入的边是否合法。具体实例化到 MST 中,就是 不断选择权最小的边,判断加入边之后是否会形成环,如果不形成环,则将边并入边集;如果形成环,那么舍弃这条边。
实例化到 Kuhn-Munkre算法中,就是选择从某一节点出发的 具有最大权值的边,判断这条边并入匹配后是否与其他匹配发生冲突,如果不发生冲突,则并入匹配中;如果发生冲突,则寻找增广路,拆除原有匹配,添加新的匹配。
以上是对Kuhn-Munkre算法的一个初步了解,下面说一下具体的实现。
首先,对二分图一侧集合中的点,找出该点连接的具有最大权值的边,并将这条边添加到一个子图中。之后,对这个子图求解最大匹配,如果每一个点都能找到一个匹配,那么原图的最大权匹配就是这些边组成的边集。
这是最理想的情况,但实际中往往会产生冲突,即有多个节点匹配到了一个点上,形成了非法匹配。
在这里插入图片描述
这样说明单纯取这几条边是不能组成最大权匹配的,因此必须向这个子图中添加一条边,来找到一条增广路添加匹配(这个添加匹配的方式和匈牙利算法一样,都是删除一个原匹配,添加两个新匹配)。
那应该选择什么样的边呢?由于要求解最大权匹配,按照算法的核心思想,便是要向这个子图中添加当前权值尽可能大的边。但是只是说尽可能大,却没有一个具体的标准也是无法实现的。
由于产生非法匹配的时候,是多个点连到了一个点上,那么我们可以肯定,要拆除的原匹配一定这几个点中的一个,而新加入的边也一定是从这几个点中的一个点出发。因为除这几个点(便于叙述记为 点集t)之外,其余的点(暂时将这些点记为 点集s)都是 最大 或 当前最优 选择,也就是说,在与点集s中点相连的边中,不会有权值更大的边可以作为这条边对应的点的匹配边且不冲突(就是说,s中每一个点的匹配边都最完美的,没有比这个更好的选择,如果有的话就不叫贪心选择了),所以点集s中的点不必考虑,只需要在点集t中找就可以了。因为要找的是最大权匹配,要保证加入的边尽可能大,换句话说,就是新加入的边 和 要拆除的边 的权值差尽可能的小,所以现在只需要遍历与点集t中的点相连的边就可以找到要加入子图的边了。
然后,不断重复匹配,加边的过程,直到所有点都找到自己的合法匹配为止,这样就找到了原图的最大权匹配。
以上就是算法实现过程的简述和说明,那么现在看看如何写具体实现的代码。
在代码实现中,引入了两个标记数组 lx 和 ly ,用于表示加入的边。其中ly数组中初始值为0,lx数组初始值为与点所连的最大权值。下面解释一下这句话:
在二分图中,记左侧集合为x,右侧集合为y。在初始时,加入的时点xi所连边中的最大权值边,若这条边为 (x1,y2) 权值为 9,那么这两个标记数组 lx[1]=9,ly[2]=0。这样通过关系式 lx[i] + ly[j] == G[i][j] 就可知道加入的边时哪一条了。
当删除原边、添加新边的时候,由于此时最大权值发生改变,进而其指代的边也发生改变,所以要修正这两个数组,使其重新指向正确的边。在遍历边的过程中,我们可以知道加入边与原边权值的最小差(记为 min),以及在查找最小差过程之前的匹配过程中为了找到匹配而走过的交替路(记为path,其中在path所经过的所有点中,属于点集 x的点,记入到集合a中;属于点集y的点,记入到集合b中),对数组的维护操作为:

lx[a<sub>i</sub>] -= min;ly[b<sub>i</sub>] += min;

假设加入的边为 (x0,yk),加入前x0所连的边为(x0,y0)。在加边操作之后,除了被删除的边(x0,y0)以外,交替路中的边原有的边都没有发生变化,只有(x0,yk)和(x0,y0)发生了变化。
在对数组的维护操作之后,我们也不难发现,点 x0 与 点 yk 之间出现了一条边,指代我们向子图中加入的边,而其余的点两两之间指代边并没有发生变化。虽然现在点 x0指向了两条边,但是这并不妨碍算法的正确执行,随后在匹配过程中就会将 x0匹配到新的匹配点,原匹配点也会有一个新的匹配。且根据增广路的轨迹可知,由于新加入的边在集合y中的点 yk没有 +min,所以数组 lx 中减小量 大于 数组 ly 中增加量,进而也表示了对应子图总权值的减小。

最后是具体的代码实现:


#include <stdio.h>
#include <climits>
#include <cstring>
#include <time.h>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <utility>
#include <vector>
#include <string>

#define MAXN 205
#define INF 0x3f3f3f3f
#define ll long long
#define Pair pair<int,int>
#define re return

#define Make(a,b) make_pair(a,b)
#define Push(num) push_back(num)
#define mem(a,b) memset(a,b,sizeof(a));
#define rep(index,star,finish) for(register int index=star;index<finish;index++)
#define drep(index,finish,star) for(register int index=finish;index>=star;index--)
using namespace std;

int N,M;
int lx[MAXN],ly[MAXN];
bool usedX[MAXN],usedY[MAXN];
int match[MAXN];
int G[MAXN][MAXN];
int Kuhn_Munkres();
bool dfs(int v);
int main(){
    ios::sync_with_stdio(false);

    int k;
    cin>>k;
    while(k--){
        cin>>N>>M;
        //initialize Graph
        rep(i,1,N+1)
            rep(j,1,N+1)
                G[i][j]=INF;
        rep(i,0,M){
            int commonA,commonB,weight;
            cin>>commonA>>commonB>>weight;
            G[commonA][commonB]=min(G[commonA][commonB],weight);
        }

        //reverse
        rep(i,1,N+1)
            rep(j,1,N+1)
                G[i][j]*=-1;

        int ans=Kuhn_Munkres();
        cout<<(-ans)<<endl;
    }

    re 0;
}

int Kuhn_Munkres(){
    //initialize
    rep(i,1,N+1){
        ly[i]=0;
        lx[i]=INT_MIN;
        rep(j,1,N+1)
            lx[i]=max(lx[i],G[i][j]);
    }

    //of lx[i],we find the max power edge in all the connected edge
    memset(match,-1,sizeof(match));
    //reset match information

    rep(u,1,N+1){
        //get match for every vertex
        while(true){
            memset(usedX,false,sizeof(usedX));
            mem(usedY,false);
            //in subgraph find matching of vertex u
            if(dfs(u))
                break;

            //if we cannot find it,add a new path to the subgraph
            int inc=INF;
            rep(i,1,1+N){
                if(usedX[i])
                    rep(j,1,N+1){
                        if(!usedY[j])
                            inc=min(inc,lx[i]+ly[j]-G[i][j]);
                    }
            }

            //maintain the connected relationship
            rep(i,1,N+1){
                if(usedX[i])
                    lx[i]-=inc;
                if(usedY[i])
                    ly[i]+=inc;
            }

        }

    }
//finish match,now we have a subgraph of max power

    int sum=0;
    rep(i,1,N+1)        //vertex in right set
        if(match[i]>0)
            sum+=G[match[i]][i];

    return sum;
}
bool dfs(int v){
    usedX[v]=true;      //record vertex in left set we have visited at this path
    rep(u,1,N+1){
        if(!usedY[u] && lx[v]+ly[u] == G[v][u]){    //why lv[x]+ly[u]==G[v][u],what's meaning?
/*
    ans of lx[v]+ly[u]==g[v][u] :
        according to top-index,find the path we added into subgraph
        just like if(adj[v][u]) in Hungary
*/
            usedY[u]=true;      //record vertex in right set we have visited  at this path
            int matched=match[u];
            if(matched<0 || dfs(matched)){      //if vertex in right set haven't matched yet or we can find a new augmenting
                match[u]=v;     //get new match
                return true;
            }
        }
    }
    return false;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值