HDU 3488 Tour (最大权完美匹配)【KM算法】

<题目链接>

题目大意:
给出n个点m条单向边边以及经过每条边的费用,让你求出走过一个哈密顿环(除起点外,每个点只能走一次)的最小费用。题目保证至少存在一个环满足条件。

解题分析

因为要求包含所有点一次的环,我们不难发现,这个环中的每个点的出度和入度均为1,所以我们不妨将每个点进行拆点,将所有点的出度和入度分为两部分。因为该环需要包括所有的点,并且题目需要求该环的最小权值,相当于该带权二分图的每个点都需要被覆盖到,由于本题就转化为求该二分图的最优完美匹配问题。二分图的最优匹配问题求解,我们会想到KM算法,但是KM是求最大权完美匹配,所以我们对每个边的权值全部取反,这时候求出的最大权值(该权值<0)的相反数就是最小权值的完美匹配了。

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N =205;
 7 #define mem(a,b) memset(a,b,sizeof(a))
 8 #define rep(i,s,t) for(int i=s;i<=t;i++)
 9 #define INF 0x3f3f3f3f
10 int n,linker[N],w[N][N],lx[N],ly[N],slack[N];
11 int visx[N],visy[N],nx,ny;
12 bool DFS(int x){
13     visx[x]=1;
14     rep(y,1,n){
15         if(visy[y]==1)continue;    //每次只常识匹配一次y,相当于匈牙利中的vis[]
16         int tmp=lx[x]+ly[y]-w[x][y];  //x,y期望值之和与x,y之间的权值的差值
17         if(!tmp){   //x,y之间期望值==他们之间权值时符合要求
18             visy[y]=1;
19             if(linker[y]==-1||DFS(linker[y])){   //y没有归属者,或者y的原始归属者能够找到其他归属者
20                 linker[y]=x;
21                 return true;
22             }
23         }else slack[y]=min(slack[y],tmp);
24     }
25     return false;
26 }
27 int KM(){
28     mem(linker,-1);mem(ly,0);   //初始化,y的期望值为0
29     rep(i,1,nx){     //初始化lx[]数组
30         lx[i]=-INF;
31         for(int j=1;j<=ny;j++){
32             lx[i]=max(lx[i],w[i][j]);   //lx为x的期望值,lx初始化为与它关联边中最大的
33         }
34     }
35     //为每一个x尝试解决归属问题
36     rep(x,1,n){
37         rep(i,1,n)slack[i]=INF;
38         while(true){
39             mem(visx,0);mem(visy,0);
40             if(DFS(x))break;//若成功(找到了增广轨),则该点增广完成,进入下一个点的增广               
41             //若失败(没有找到增广轨),则需要改变一些点的标号,使得图中可行边的数量增加。
42             //方法为:将所有在增广轨中(就是在增广过程中遍历到)的X方点的标号全部减去一个常数d,
43             //所有在增广轨中的Y方点的标号全部加上一个常数d
44             int d=INF;
45             rep(i,1,ny)if(!visy[i])d=min(d,slack[i]);   //d为没有匹配到的y的slack中的最小值
46             rep(i,1,nx)if(visx[i])lx[i]-=d;
47             rep(i,1,ny)
48                 if(visy[i])ly[i]+=d;
49                 else slack[i]-=d;      //修改顶标后,要把所有不在交错树中的Y顶点的slack值都减去d
50         }
51     }
52     int res=0;
53     rep(i,1,ny){
54         if(linker[i]!=-1)
55             res+=w[linker[i]][i];
56     }
57     return res;
58 }
59 /*--  以上为KM算法模板   --*/
60 int main(){
61     int T,m,u,v,c;scanf("%d",&T);
62     while(T--){
63         scanf("%d%d",&n,&m);
64         rep(i,1,n) rep(j,1,n){
65             w[i][j]=-INF;
66         }
67         //将每个点进行拆点,分成出度(x部分)和入度(y部分)两部分来处理
68         nx=ny=n;
69         rep(i,1,m){
70             scanf("%d%d%d",&u,&v,&c);
71             if(w[u][v]<-c)    //因为要求最小的权值,而KM算法是求最大的权值,所以这里将所有边的权值取反,这样用KM算出的最大值的相反数就是最小值了
72                 w[u][v]=-c;   //去重边,取权值最小的边
73         }
74         printf("%d\n",-1*KM());   //对求出的最大值取反即可
75     }
76 }

2018-11-18

猜你喜欢

转载自www.cnblogs.com/00isok/p/9977664.html