题目概述
在平面直角坐标系中有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;
}