【KM】POJ3565[Ants]题解

题目概述

在平面直角坐标系中有n个蚂蚁窝和n个苹果树(都是点),保证没有三点共线,一个蚂蚁窝只能匹配一个苹果树,一个苹果树也只能匹配一个蚂蚁窝。求一个匹配,使得没有任意两条连线是相交的。

解题报告

把苹果树看成X集合,蚂蚁看成Y集合,那么这显然是一张二分图。但是如何满足不交叉?我们会发现,交叉肯定不存在距离最优解中,因为:
这里写图片描述
左图还不如右图。

所以我们刷出最佳完美匹配,此时的匹配就是一组满足的解。
需要注意的是,KM算法求最小和求最大不是很一样,所以我们可以把边权变成相反数,这样刷最小就等同于刷最大,最后答案取相反数就行了(虽然这道题不用刷答案)。

示例程序

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=100;

int n,who[maxn+5];
double Lx[maxn+5],Ly[maxn+5];
double MINs[maxn+5],cst[maxn+5][maxn+5];
bool S[maxn+5],T[maxn+5];
struct Point {int x,y;};
Point a[maxn+5],t[maxn+5];

int sqr(int x) {return x*x;}
double getdis(Point &a,Point &b) {return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}
bool Find(int x)
{
    S[x]=true;
    for (int y=1;y<=n;y++) if (!T[y])
    {
        double s=Lx[x]+Ly[y]-cst[x][y];
        if (fabs(s)<1e-10)
        {
            T[y]=true;
            if (!who[y]||Find(who[y])) {who[y]=x;return true;}
        } else
        MINs[y]=min(MINs[y],s);
    }
    return false;
}
void KM()
{
    for (int i=1;i<=n;i++) Lx[i]=-(1e100);
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
        Lx[i]=max(Lx[i],cst[i][j]);
    memset(Ly,0,sizeof(Ly));
    memset(who,0,sizeof(who));
    for (int now=1;now<=n;now++)
    {
        for (int i=1;i<=n;i++) MINs[i]=1e100;
        while (true)
        {
            memset(S,0,sizeof(S));memset(T,0,sizeof(T));
            if (Find(now)) break;
            double MIN=1e100;
            for (int i=1;i<=n;i++) if (!T[i]) MIN=min(MIN,MINs[i]);
            for (int i=1;i<=n;i++)
            {
                if (S[i]) Lx[i]-=MIN;
                if (T[i]) Ly[i]+=MIN; else
                MINs[i]-=MIN;
            }
        }
    }
}
int main()
{
    freopen("program.in","r",stdin);
    freopen("program.out","w",stdout);
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d%d",&t[i].x,&t[i].y);
    for (int i=1;i<=n;i++) scanf("%d%d",&a[i].x,&a[i].y);
    for (int i=1;i<=n;i++)
    for (int j=1;j<=n;j++)
        cst[i][j]=-getdis(a[i],t[j]);
    KM();
    for (int i=1;i<=n;i++) printf("%d\n",who[i]);
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值