帶權二分圖匹配KM演算法
阿新 • • 發佈:2018-12-24
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=1e9;
int flag;
struct zp
{
int x,y;
}man[200],house[200];
int x[200][200],mn,ho,lx[200],ly[200];
void build_map()//建圖,單向圖
{
for(int i=0; i<mn; i++)
{
for(int j=0 ; j<ho; j++)
{
x[i+1][j+1]=abs(man[i].x-house[j].x)+abs(man[i].y-house[j].y);
}
}
}
int visx[200],visy[200],dis[200],ans;
int dfspath(int k)//匈牙利演算法,尋找路徑
{
visx[k]=1;
for(int i=1; i<=mn; i++)
{
if(!visy[i]&&lx[k]+ly[i]==x[k][i])
{
visy[i]=1 ;
if(dis[i]==-1||dfspath(dis[i]))
{
dis[i]=k;
return 1;
}
}
}
return 0;
}
int KM()
{
if(!flag)//最小權值處理
{
for(int i=1;i<=mn;i++)
for(int j=1;j<=ho;j++)
x[i][j]=-x[i][j];
}
for (int i=1;i<=mn;i++)//初始化x點集標號
{
lx[i]=x[i][1];
for(int j=2;j<=ho;j++)
lx[i]=max(lx[i],x[i][j]);
}
memset(ly,0,sizeof(ly));//初始化y點集標號
memset(dis,-1,sizeof(dis));//儲存對應關係
for(int i=1; i<=mn; i++)
{
while(1)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(dfspath(i))//找到路徑
break;
int Min=INF+1;
for(int j=1;j<=mn;j++)//擴大子圖範圍
{
if(visx[j])
for(int k=1;k<=ho;k++)
if(!visy[k])
Min=min(Min,lx[j]+ly[k]-x[j][k]);
}
for(int j=1;j<=mn;j++)//更新x點集標號
if(visx[j])
lx[j]-=Min;
for(int j=1;j<=ho;j++)//更新y點集標號
if(visy[j])
ly[j]+=Min;
}
}
ans=0;
for(int i=1;i<=ho;i++)//計算權值和1
ans+=x[dis[i]][i];
if(!flag)//求最小權值
ans=-ans;
return ans;//返回權值
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m)&&(n||m))
{
mn=ho=0;
char a[200];
memset(x,0,sizeof(x));
//flag=1;//最大權匹配
flag=0;//最小權匹配 ,把權值取反然後求最大權匹配然後把結果取反
for(int i=0; i<n; i++)
{
scanf("%s",a);
for(int j=0; j<m; j++)
{
if(a[j]=='m') man[mn].x=i+1,man[mn++].y=j+1;
else if(a[j]=='H') house[ho].x=i+1,house[ho++].y=j+1;
}
}
build_map();
printf("%d\n",KM());
}
}
上面題目連結
題目大概意思是讓求所有的m到H走的所有路之和最小,每一個H只能承載1個m。
KM的一點優化,有兩種方式
hdu 2255題目
一般解法:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=1e9;
int flag;
int x[500][500],n,lx[500],ly[500];
int visx[500],visy[500],dis[500],ans;
int dfspath(int k)//匈牙利演算法,尋找路徑
{
visx[k]=1;
for(int i=0; i<n; i++)
{
if(!visy[i]&&lx[k]+ly[i]==x[k][i])
{
visy[i]=1;
if(dis[i]==-1||dfspath(dis[i]))
{
dis[i]=k;
return 1;
}
}
}
return 0;
}
int KM()
{
// if(!flag)//最小權值處理
// {
// for(int i=0;i<=n;i++)
// for(int j=0;j<n;j++)
// x[i][j]=-x[i][j];
// }
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));//初始化y點集標號
memset(dis,-1,sizeof(dis));//儲存對應關係
for(int i=0; i<n; i++) //初始化x點集標號
for(int j=0; j<n; j++)
lx[i]=max(lx[i],x[i][j]);
for(int i=0; i<n; i++)
{
while(1)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
if(dfspath(i))//找到路徑
break;
int Min=INF+1;
for(int j=0; j<n; j++) //擴大子圖範圍
{
if(visx[j])
for(int k=0; k<n; k++)
if(!visy[k])
Min=min(Min,lx[j]+ly[k]-x[j][k]);
}
for(int j=0; j<n; j++) //更新x,y點集標號
{
if(visx[j])
lx[j]-=Min;
if(visy[j])
ly[j]+=Min;
}
}
}
ans=0;
for(int i=0; i<n; i++) //計算權值和1
ans+=x[dis[i]][i];
// if(!flag)//求最小權值
// ans=-ans;
return ans;//返回權值
}
int main()
{
while(~scanf("%d",&n))
{
//flag=1;//最大權匹配
//flag=0;//最小權匹配 ,把權值取反然後求最大權匹配然後把結果取反
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
scanf("%d",&x[i][j]);
//build_map();
printf("%d\n",KM());
}
}
優化後的:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int INF=1e9;
int flag;
int x[500][500],n,lx[500],ly[500];
int visx[500],visy[500],dis[500],ans,slack[500],Min;
void build_map()//建圖,單向圖
{
}
int dfspath(int k)//匈牙利演算法,尋找路徑
{
visx[k]=1;
for(int i=0; i<n; i++)
{
if(visy[i])
continue;
int t=lx[k]+ly[i]-x[k][i];
if(t==0)
{
visy[i]=1;
if(dis[i]==-1||dfspath(dis[i]))
{
dis[i]=k;
return 1;
}
}
else
//slack[i]=min(slack[i],t);//優化1
Min=min(Min,t);//優化2
}
return 0;
}
int KM()
{
// if(!flag)//最小權值處理
// {
// for(int i=0; i<n; i++)
// for(int j=0; j<n; j++)
// x[i][j]=-x[i][j];
// }
memset(lx,0,sizeof(lx));
memset(ly,0,sizeof(ly));//初始化y點集標號
memset(dis,-1,sizeof(dis));//儲存對應關係
for(int i=0; i<n; i++) //初始化x點集標號
for(int j=0; j<n; j++)
lx[i]=max(lx[i],x[i][j]);
for(int i=0; i<n; i++)
{
// for(int j=0;j<=n;j++)//優化1
// slack[j]=INF;
while(1)
{
memset(visx,0,sizeof(visx));
memset(visy,0,sizeof(visy));
Min=INF+1;
if(dfspath(i))//找到路徑
break;
// for(int j=0; j<n; j++)//擴大子圖範圍 優化1 優化2不要這一步
// if(!visy[j])
// Min=min(Min,slack[j]);
for(int j=0; j<n; j++) //更新x點集標號
{
if(visx[j])
lx[j]-=Min;
if(visy[j])
ly[j]+=Min;
// else//有這一步會超時,但是有的人部落格上有這一步,參考下
// slack[i]-=Min;
}
}
}
ans=0;
for(int i=0; i<n; i++) //計算權值和
ans+=x[dis[i]][i];
// if(!flag)//求最小權值
// ans=-ans;
return ans;//返回權值
}
int main()
{
while(~scanf("%d",&n))
{
flag=1;
for(int i=0; i<n; i++)
for(int j=0; j<n; j++)
scanf("%d",&x[i][j]);
printf("%d\n",KM());
}
}