Uvalive3353 Optimal Bus Route Design 带权二分图匹配

题目描述:给出一个有向带权图,现在要求在图中找出若干个环,使得每个点恰好在一个环里,且所有环的距离之和最小,如果不能使每个点恰好在一个环里,输出"N"。

思路:

      将每个点u拆成u和u'两个点,如果从u到v有一条权值为dist的边,那么就从u向v'连一条权值为dist的边,明显可以看出,现在的这个图是一个二分图。下面就是这个问题的关键——在这个二分图中,每一个完美匹配对应了原图中的一组互不相交且把每个点都囊括在内的环;反之也成立 ,即原图中的一组互不相交且把每个点都囊括在内的环正好对应了二分图中的一个完美匹配。为什么呢?因为在一组互不相交的环中,每个点的入度为一,出度为一;而在二分图的一个完美匹配中,一个点u的u对应一条边,u'对应一条边,即每个点u的入度为一,出度也为一,结论得证,因此,二分图中的一个完美匹配和原图中的一组互不相交的环一一对应。

       分析到这里,做法就比较明朗了。原来是要求原图中的一组互不相交且把每个点都囊括在内的权值最小的环,现在就变成求一个权值最小的完美匹配,就是一个二分图带权匹配的问题。注意是最小匹配,每条边权应该取原来的相反数,不存在于图上的边设为-INF,跑二分图最大匹配就可以,跑完了结果取相反数,如果最终结果大于等于INF,就说明原图不存在完美匹配,那么输出“N”

理解:

      这种转换如果是第一次见,似乎真的不太好想,见过一次之后就加以总结——二分图的一个匹配,对应于原图的一组互不相交的环,反之亦然

#include <bits/stdc++.h>
#define LL long long
#define mem(a , x) memset(a , x , sizeof(a))
using namespace std;
const int maxn = 100 + 5;
const int INF = 1e5;
int S[maxn] , T[maxn] , lx[maxn] , ly[maxn] , link[maxn] , slack[maxn];
int mp[maxn][maxn] , n;
int dfs(int u)
{
    S[u] = 1;
    for(int i = 1 ; i <= n ; i++){
        int d = lx[u] + ly[i] - mp[u][i];
        if(!T[i] && d == 0){
            T[i] = 1;
            if(link[i] == -1 || dfs(link[i])){
                link[i] = u;
                return 1;
            }
        }
        else
            slack[i] = min(slack[i] , d);
    }
    return 0;
}
int KM()
{
    int res = 0;
    mem(link , -1);
    for(int i = 1 ; i <= n ; i++){
        lx[i] = -INF; ly[i] = 0;
        for(int j = 1 ; j <= n ;j++){
            lx[i] = max(lx[i] , mp[i][j]);
        }
    }
    for(int i = 1 ; i <= n ; i++){
        mem(S , 0); mem(T , 0);
        for(int j = 1 ; j <= n ; j++)   slack[j] = INF;
        while(!dfs(i)){
            int d = INF;
            for(int j = 1 ; j <= n ;j++){
                if(!T[j] && slack[j] < d){
                    d = slack[j];
                }
            }
            for(int j = 1 ; j<= n ; j++){
                if(S[j]){
                    lx[j] -= d;   S[j] = 0;
                }
                if(T[j]){
                    ly[j] += d;   T[j] = 0;
                }
            }
        }
    }
    for(int i = 1 ; i <= n ;i++){
        res += (lx[i] + ly[i]);
    }
    return res;
}
int main()
{
    while(scanf("%d" , &n) != EOF && n){
        for(int i = 1 ; i <= n ; i++){
            for(int j = 1 ;j <= n ;j++){
                mp[i][j] = -INF;
            }
        }
        int to , dis;
        for(int i = 1 ; i <= n ; i++){
            while(scanf("%d" , &to) && to){
                scanf("%d" ,&dis);
                mp[i][to] = -dis;
            }
        }
        int ans = -KM();
        if(ans > 10000) puts("N");
        else            printf("%d\n" , ans);
    }
    return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值