【玲珑杯 1047】【二分匹配 KM算法或者费用流】Best couple【定义男女生的距离为最短距离,求匹配之后使得总距离最大】

传送门:http://www.ifrog.cc/acm/problem/1055

思路:

跑一发floyd,然后再用km。

但是问题来了,这个有可能n != m。那怎么办?

其实可以补上一些不存在的点。来使得n = m。他们的权值就设置为0就好了。意思就是这些人的搭配,是对答案没有贡献的。注意不能设置为-inf。因为补上的那些点也是必须要选人的,只不过他们选了人,相当于没选而已(权值不存在。)如果设置为-inf的那些,那么他们就会把答案改了。


还有一个小trick的就是,一开始,我是把本来地图上的-1的那些点,更改为inf的,inf表示不连通,那么直接floyd就可以了不用特判那么多。那么问题又来了。如果跑了floyd后,还是不连通,那怎么办?他们的权值可是inf啊。组成新图的时候,同时也是需要把他们的权值设置为0的,也就是相当于没选。


或者直接跑费用流省去建虚点的麻烦


下面给出一些概念:

1.通俗地说,完全匹配只要一边的点都在匹配中就行。而完美匹配则是相当于要“双向的完全匹配”。
2.KM算法求的是完美匹配中权最大的一个,在本来图就达不到完美匹配,而只有若干完备匹配的情况下,KM不能求出完备匹配中权最大的一个。
3.如果本来图就达不到完美匹配,但仍要求出最大权匹配,那么该怎么办?
(1)建虚点虚边,只要把所有没有的边设成0就行了,然后跑KM算法
(2)用费用流来跑,这种情况用费用来跑相对来说代码少点,但是效率比KM稍慢

代码一:

(费用流 266ms)

#include <bits/stdc++.h>
using  namespace  std;

//最小费用最大流,求最大费用只需要取相反数结果即可。
//点的总数为 N,点的编号 0~N 
const int maxn=205;
const int maxm=100005;
const int inf=0x3f3f3f3f;

struct Edge{
  int to,next,cap,flow,cost;
}es[maxm];
int head[maxn],tol;
int p[maxn];//记录增广路径上 到达点i的边的编号 
int d[maxn];//单位总费用
bool vis[maxn];
int N;//节点总个数
int dp[maxn][maxn];
int n, m;

void init(int n){
  N=n;
  tol=0;
  memset(head, -1, sizeof(head));
}

void addedge(int u,int v,int cap,int cost){
  es[tol].to=v;
  es[tol].cap=cap;
  es[tol].cost=cost;
  es[tol].flow=0;
  es[tol].next=head[u];
  head[u]=tol++;
  es[tol].to=u;
  es[tol].cap=0;
  es[tol].cost=-cost;
  es[tol].flow=0;
  es[tol].next=head[v];
  head[v]=tol++;
}

bool spfa(int s,int t){//寻找花销最少的路径 
  //跑一遍SPFA 找s——t的最少花销路径 且该路径上每一条边不能满流  
   //若存在 说明可以继续增广,反之不能  
  queue<int>q;
  for(int i=0; i<=N; i++){
    d[i]=inf;
    vis[i]=false;
    p[i]=-1;
  }
  d[s]=0;
  vis[s]=true;
  q.push(s);
  while(!q.empty()){
    int u=q.front();
    q.pop();
    vis[u]=false;
    for(int i=head[u]; i!=-1; i=es[i].next){ //逆向枚举以u为起点的边
      int v=es[i].to;
      if(es[i].cap>es[i].flow && d[v]>d[u]+es[i].cost){//可以松弛 且 没有满流 
        d[v]=d[u]+es[i].cost;
        p[v]=i;   //记录前驱边 的编号 
        if(!vis[v]){ vis[v]=true; q.push(v);}
      }
    }
  }
  return p[t]!=-1;//可达返回true
}

//返回的是最大流,cost存的是最小费用
int MCMF(int s,int t,int &cost){
  int flow = 0;cost = 0;
  while(spfa(s,t)){//每次寻找花销最小的路径 
    int Min=inf;
    //通过反向弧 在源点到汇点的最少花费路径 找最小增广流  
    for(int i=p[t]; i!=-1; i=p[es[i^1].to]){
      if(Min>es[i].cap-es[i].flow)
      Min=es[i].cap-es[i].flow;
    }
    //增广
    for(int i=p[t]; i!=-1; i=p[es[i^1].to]){
      es[i].flow+=Min;
      es[i^1].flow-=Min;
      cost+=es[i].cost*Min;//增广流的花销 
    }
    flow+=Min;//总流量累加 
  }
  return flow;
}

void floyd(){
    for(int k=0; k<n+m; k++)
        for(int i=0; i<n+m; i++)
            for(int j=0; j<n+m; j++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}

int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &m);
        for(int i=0; i<n+m; i++)
            for(int j=0; j<n+m; j++){
                scanf("%d", &dp[i][j]);
                if(dp[i][j] == -1)dp[i][j] = inf;
            }
        floyd();
        init(n+m+2);
        for(int i=0; i<n; i++)
            addedge(n+m, i, 1, 0);//源点与男生之间建边
        for(int i=n; i<n+m; i++)
            addedge(i, n+m+1, 1, 0);//汇点与女生之间建边
        for(int i=0; i<n; i++)
            for(int j=n; j<n+m; j++)
                if(dp[i][j] < inf)
                    addedge(i, j, 1, -dp[i][j]);//男生与女生之间距离小于inf建边
        int cost;
        MCMF(n+m, n+m+1, cost);
        printf("%d\n", -cost);
    }  
    return 0;
}


代码二:

(KM 126ms)

#include <bits/stdc++.h>
using  namespace  std;
#define mst(ss,b) memset(ss,b,sizeof(ss));

/* KM算法
* 复杂度O(nx*nx*ny)
* 求最大权匹配
* 若求最小权匹配,可将权值取相反数,结果取相反数
* 点的编号从1开始
*/
const int N=105;
const int inf=0x3f3f3f3f;
int nx,ny;//两边的点数
int g[N][N];//二分图描述
int link[N],lx[N],ly[N];//y中各点匹配状态,x,y中的顶点标号
int slack[N];
bool visx[N],visy[N];
int dp[2*N][2*N];
int n, m;

bool dfs(int x){
  visx[x]=true;
  for(int y=1; y<=ny; y++){
    if(visy[y])continue;
    int tmp=lx[x]+ly[y]-g[x][y];
    if(tmp==0){
      visy[y]=true;
      if(link[y]==-1 || dfs(link[y])){
        link[y]=x;
        return true;
      }
    }
    else if(slack[y]>tmp) slack[y]=tmp;
  }
  return false;
}

int KM(){
  memset(link, -1, sizeof(link));
  memset(ly, 0, sizeof(ly));
  for(int i=1; i<=nx; i++){
    lx[i]=-inf;
    for(int j=1; j<=ny; j++)
      if(g[i][j]>lx[i])
        lx[i]=g[i][j];
  }
  for(int x=1; x<=nx; x++){
    for(int i=1; i<=ny; i++) slack[i]=inf;
    while(true){
      memset(visx, false, sizeof(visx));
      memset(visy, false, sizeof(visy));
      if(dfs(x))break;
      int d=inf;
      for(int i=1; i<=ny; i++)
        if(!visy[i] && d>slack[i])
          d=slack[i];
      for(int i=1; i<=nx; i++)
        if(visx[i])
          lx[i]-=d;
      for(int i=1; i<=ny; i++){
        if(visy[i])ly[i]+=d;
        else slack[i]-=d;
      }
    }
  }
  int res=0;
  for(int i=1; i<=ny; i++)
  if(link[i]!=-1)
    res+=g[link[i]][i];
  return res;
}

void floyd(){
    for(int k=0; k<n+m; k++)
        for(int i=0; i<n+m; i++)
            for(int j=0; j<n+m; j++)
                dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j]);
}

int  main(){
    int T;
    scanf("%d", &T);
    while(T--){
        mst(dp, 0);
        scanf("%d%d", &n, &m);
        for(int i=0; i<n+m; i++)
            for(int j=0; j<n+m; j++){
                scanf("%d", &dp[i][j]);
                if(dp[i][j] == -1)dp[i][j] = inf;
            }
        floyd();

        for(int i=1; i<=n; i++)
                for(int j=1; j<=m; j++){
                    g[i][j]=dp[i-1][n+j-1];
                    if(g[i][j] >= inf)g[i][j]=0;
                }

        if(n < m){
            for(int i=n+1; i<=m; i++)
                for(int j=1; j<=m; j++)
                    g[i][j]=0;
        }
        else if(n > m){
            for(int i=1; i<=n; i++)
                for(int j=m+1; j<=n; j++)
                    g[i][j]=0;
        }

        nx = ny = max(n, m);      
        printf("%d\n", KM());
    }  
    return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值