<题目链接>
题目大意:
给出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
原文地址:https://www.cnblogs.com/00isok/p/9977664.html
时间: 2024-10-07 06:13:45