KM小练

1、题目:

HDU1853 Cyclic Tour

题意:

给出一个n个点m条边的有向图,每一条边有边权,从这个图中选出若干个环,使每一个点属于且仅属于一个环,并且使环的权值和最小。

题解:

看到这个东西要想到拆点,然后就用KM求个最大带权匹配就好了,因为要求最小值,就把所有的变成负数求最大值再倒过来
注意如果发现一组不能匹配就直接返回,不要继续累加,不然容易爆int而变成一个极大的负数

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define INF 1e9
using namespace std;
int n,vis[205],lx[105],ly[105],e[105][105],belong[105];
int find(int i,int id)
{
    vis[i]=id;
    for (int j=1;j<=n;j++)
      if (vis[j+n]!=id && lx[i]+ly[j]==e[i][j])
      {
        vis[j+n]=id;
        if (!belong[j] || find(belong[j],id))
        {
            belong[j]=i;
            return 1;
        }
      }
    return 0;
}
void change(int id)
{
    int i,j,a=INF;
    for (i=1;i<=n;i++)
      if (vis[i]==id)
        for (j=1;j<=n;j++)
          if (vis[j+n]!=id) a=min(a,lx[i]+ly[j]-e[i][j]);
    for (i=1;i<=n;i++)
    {
        if (vis[i]==id) lx[i]-=a;
        if (vis[i+n]==id) ly[i]+=a;
    }
}
int KM()
{
    int i,j,ans=0,id=0;
    memset(vis,0,sizeof(vis));
    for (i=1;i<=n;i++)
    {
        belong[i]=0; lx[i]=-INF;ly[i]=0;
        for (j=1;j<=n;j++) lx[i]=max(lx[i],e[i][j]);
    }
    for (i=1;i<=n;i++)
      while (1)
      {
        ++id;
        if (find(i,id)) break;
        else change(id);
      }
    for (i=1;i<=n;i++)
      if (e[belong[i]][i]<-INF) return 1;else ans+=e[belong[i]][i];
    return ans;
}
int main()
{
    int m,i;
    while (scanf("%d%d",&n,&m)!=EOF)
    {
        memset(e,128,sizeof(e));
        for (i=1;i<=m;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            e[x][y]=max(e[x][y],-z);
        }
        printf("%d\n",-KM());
    }
}

2、题目:

HDU2426 Interesting Housing Problem

题意:

有n个学生,m个房间,每一个学生对一些房间有一个评分,求使所有的学生每一个人分配一个房间并使评分最大
注意这道题学生不会分配一个没有评分或者评分为负数的房间

题解:

这道题调了好久啊,最后发现是初始化出了问题,果然m和n不相等很麻烦。。
别的就是一样的KM最大匹配,注意因为不是每一个房间都是有人住,要记录住上宿舍的学生个数,看看是不是n
这是一种优化的KM,其实是一样的,只不过在find的时候就维护了顶标,效率高出一倍呢,以后还是要写这种啊

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define INF 1e9
using namespace std;
int vis[1005],w[505][505],belong[505],lx[505],ly[505],n,m,delta[505];
bool find(int i,int id)
{
    vis[i]=id;
    for (int j=1;j<=m;j++)
      if (vis[j+n]!=id)
      {
        if (lx[i]+ly[j]==w[i][j])
        {
            vis[j+n]=id;
            if (!belong[j] || find(belong[j],id))
            {
                belong[j]=i;
                return 1;
            }
        }else delta[j]=min(delta[j],lx[i]+ly[j]-w[i][j]);
      }
    return 0;
}
int KM()
{
    int i,j,ans=0,ii,id=0;
    memset(vis,0,sizeof(vis));
    for (i=1;i<=n;i++)
    {
        lx[i]=-INF;
        for (j=1;j<=m;j++) lx[i]=max(lx[i],w[i][j]),ly[j]=0,belong[j]=0;
    }
    for (ii=1;ii<=n;ii++)
    {
        for (j=1;j<=m;j++) delta[j]=INF;
        while (1)
        {
            ++id;
            if (find(ii,id)) break;
            int a=INF;
            for (i=1;i<=m;i++) 
              if (vis[i+n]!=id) a=min(a,delta[i]);
            for (i=1;i<=n;i++)
              if (vis[i]==id) lx[i]-=a;
            for (i=1;i<=m;i++)
              if (vis[i+n]==id) ly[i]+=a;
              else delta[i]-=a;
        }
    }
    int xs=0;
    for (i=1;i<=m;i++)
      if (belong[i] && w[belong[i]][i]>-INF) ans+=w[belong[i]][i],xs++;
    if (xs<n) return -1;
    return ans;
}
int main()
{
    int num=0,e,i;
    while (~scanf("%d%d%d",&n,&m,&e))
    {
        memset(w,0x8f,sizeof(w));
        for (i=1;i<=e;i++)
        {
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            if (z<0) continue;
            x++;y++;w[x][y]=z;
        }
        printf("Case %d: %d\n",++num,KM());
    }
}

3、题目:

POJ3565 Ants

题意:

在一个二维平面上给出n个黑点和n个白点,求一种方案使黑点白点配对形成的n条线段不相交。

题解:

相交?这个KM有什么关系呢?但是让我们画个图来体会一下
这里写图片描述
我们就是害怕出现蓝边这种情况啊,我们比较喜欢粉边这一种呢
但是你可以发现了吧?蓝边相加一定大于粉边啊,所以我们只需要找最小边匹配就好了!

还有一个小细节,如果我们想输出结果是belong的话就太好啦,怎么办呢?暗箱把蚂蚁和窝换一下呗

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#define INF 1e9
using namespace std;
const double eps=1e-9;
struct hh{int x,y;}ren[105],hou[105];
int n,m,vis[205],belong[105],ans[105];
double e[105][105],delta[105],lx[105],ly[105];
bool find(int i,int id)
{
    vis[i]=id;
    for (int j=1;j<=n;j++)
      if (vis[j+n]!=id)
      {
        if ((lx[i]+ly[j]-e[i][j])<=eps && (lx[i]+ly[j]-e[i][j])>=-eps)
        {
            vis[j+n]=id;
            if (!belong[j] || find(belong[j],id))
            {
                belong[j]=i;
                return 1;
            }
        }else delta[j]=min(delta[j],lx[i]+ly[j]-e[i][j]);
      }
    return 0;
}
void KM()
{
    int i,j,id=0,ii;
    memset(lx,0x8f,sizeof(lx));
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++) lx[i]=max(lx[i],e[i][j]);

    for (ii=1;ii<=n;ii++)
    {
        for (j=1;j<=n;j++) delta[j]=INF;
        while (1)
        {
            id++;
            if (find(ii,id)) break;
            double a=INF;
            for (i=1;i<=n;i++) 
              if (vis[i+n]!=id) a=min(delta[i],a);
            for (i=1;i<=n;i++)
            {
                if (vis[i]==id) lx[i]-=a;
                if (vis[i+n]==id) ly[i]+=a;
                else delta[i]-=a; 
            }     
        }
    }
}
int main()
{
    int i,j;
    scanf("%d",&n);
    for (i=1;i<=n;i++) scanf("%d%d",&hou[i].x,&hou[i].y);
    for (i=1;i<=n;i++) scanf("%d%d",&ren[i].x,&ren[i].y);
    for (i=1;i<=n;i++)
      for (j=1;j<=n;j++)
        e[i][j]=-sqrt((double)(ren[i].x-hou[j].x)*(ren[i].x-hou[j].x)+(double)(ren[i].y-hou[j].y)*(ren[i].y-hou[j].y));
    KM();
    for (i=1;i<=n;i++) printf("%d\n",belong[i]);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值