题目描述
传送门
题目大意:给出n个黑点和n个白点的坐标,要求两两连线,使所有的线段不相交
题解
dis(a,b)+dis(c,d)<dis(b,d)+dis(a,c)
怎么证明? 根据三角形两边之和大于第三边
dis(a,b)<dis(a,o)+dis(b,o)
dis(c,d)<dis(c,o)+dis(d,o)
将两个式子合并就可以得到上面的式子。
那么我们在建图的时候,将每个黑白点之间的边权赋值成两个点距离的相反数,然后跑KM算法就可以了。
因为KM每次选择的时候一定是尽可能最大化方案的权值和,所以每加入一对,一定满足上面的式子。
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 320
#define LL long long
#define eps 1e-8
using namespace std;
const double inf=1e17;
double ax[N],ay[N],bx[N],by[N];
int belong[N],pos[N],n,pdx[N],pdy[N];
double slack[N],cx[N],cy[N],val[N][N];
double pow(double x){
return x*x;
}
double get_dis(double x,double y,double x1,double y1)
{
return sqrt(pow(x-x1)+pow(y-y1));
}
bool dfs(int x)
{
pdx[x]=1;
for (int i=1;i<=n;i++) {
if (pdy[i]) continue;
double gap=cx[x]+cy[i]-val[x][i];
if (fabs(gap)<=eps){
pdy[i]=1;
if (!belong[i]||dfs(belong[i])) {
belong[i]=x;
return true;
}
}else slack[i]=min(slack[i],gap);
}
return false;
}
void km()
{
memset(cy,0,sizeof(cy));
memset(belong,0,sizeof(belong));
for (int i=1;i<=n;i++) {
cx[i]=val[i][1];
for (int j=2;j<=n;j++) cx[i]=max(cx[i],val[i][j]);
}
for (int i=1;i<=n;i++) {
for (int j=1;j<=n;j++) slack[j]=inf;
while (true) {
memset(pdx,0,sizeof(pdx));
memset(pdy,0,sizeof(pdy));
if (dfs(i)) break;
double d=inf;
for (int j=1;j<=n;j++)
if (!pdy[j]) d=min(d,slack[j]);
for (int j=1;j<=n;j++) {
if (pdx[j]) cx[j]-=d;
if (pdy[j]) cy[j]+=d;
else slack[j]-=d;
}
}
}
}
int main()
{
freopen("a.in","r",stdin);
scanf("%d",&n);
for (int i=1;i<=n;i++) scanf("%lf%lf",&ax[i],&ay[i]);
for (int j=1;j<=n;j++) scanf("%lf%lf",&bx[j],&by[j]);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) val[i][j]=-get_dis(ax[i],ay[i],bx[j],by[j]);
km();
for (int i=1;i<=n;i++) pos[belong[i]]=i;
for (int i=1;i<=n;i++) printf("%d\n",pos[i]);
}