题意:给你一个由N个港口和M个海上油田构成的连通无向图(给出了图中所有的边和权值),现在给你N个船所在的油田编号,问你让这N条船,每条都回到1个港口去(每个港口最多只能容纳一条船),问你这N条船行走的总距离最短是多少?
思路:其实每条船回到任意一个港口去都有一个距离(用Floyd算法算出的最短距离). 建立二分图: 我们把二分图左边放N个港口,右边放N条船,如果第j条船到第i个港口的距离为x,那么就在右j点与左i点之间连一条权值为x的边.答案即为求 该二分图的最优匹配权值是多少?
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=100+5;
struct Max_Match
{
int n,W[maxn][maxn];
int Lx[maxn],Ly[maxn];
bool S[maxn],T[maxn];
int left[maxn];
bool match(int i)
{
S[i]=true;
for(int j=1;j<=n;j++)if(Lx[i]+Ly[j]==W[i][j] && !T[j])
{
T[j]=true;
if(left[j]==-1 || match(left[j]))
{
left[j]=i;
return true;
}
}
return false;
}
void update()
{
int a=1<<30;
for(int i=1;i<=n;i++)if(S[i])
for(int j=1;j<=n;j++)if(!T[j])
a=min(a, Lx[i]+Ly[j]-W[i][j]);
for(int i=1;i<=n;i++)
{
if(S[i]) Lx[i] -=a;
if(T[i]) Ly[i] +=a;
}
}
int solve(int n)
{
this->n=n;
memset(left,-1,sizeof(left));
for(int i=1;i<=n;i++)
{
Lx[i]=Ly[i]=0;
for(int j=1;j<=n;j++)
Lx[i]=max(Lx[i],W[i][j]);
}
for(int i=1;i<=n;i++)
{
while(true)
{
for(int j=1;j<=n;j++) S[j]=T[j]=false;
if(match(i)) break;
else update();
}
}
int ans=0;
for(int i=1;i<=n;i++)
ans += W[left[i]][i];
return -ans;//注意取负数
}
}KM;
#define INF 1e9
int dist[300+10][300+10];
void floyd(int n)
{
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dist[i][k]< INF && dist[k][j]<INF)
dist[i][j] = min(dist[i][j], dist[i][k]+dist[k][j]);
}
int main()
{
int n,m,k,p;
int station_id[maxn];
while(scanf("%d%d%d%d",&n,&m,&k,&p)==4)
{
for(int i=1;i<=n;i++)
scanf("%d",&station_id[i]);
for(int i=1;i<=n+m;i++)
for(int j=1;j<=n+m;j++)
dist[i][j]= i==j?0:INF;
while(k--)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
dist[u+n][v+n]=dist[v+n][u+n]=w;
}
while(p--)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
dist[u][v+n]=w;//港口到油田是单向的
}
floyd(n+m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
KM.W[i][j] = -dist[i][station_id[j]+n];//注意取负数
printf("%d\n",KM.solve(n));
}
return 0;
}