训练指南 UVALive - 4043(二分图匹配 + KM算法)

时间:2022-05-07 21:37:35

layout: post

title: 训练指南 UVALive - 4043(二分图匹配 + KM算法)

author: "luowentaoaa"

catalog: true

mathjax: true

tags:

- 二分图匹配

- 图论

- 训练指南


Ants

UVALive - 4043

题意

给你n个白点和n个黑点的平面坐标,要求用n条不相交的线连起来,每条线段连一个白点和黑点,每个点连一条线,也就是匹配。让你输出第i个白点所对应的黑点。

思路

二分图完美匹配问题。但是题目中有个线段不相交,怎么办?其实这个最佳完美匹配就是答案了。最佳完美匹配是权值和最大,那么我们就把两两点线段的权值搞成他们距离的负数即可。这样就不可能有相交的了。为什么?因为假设有相交,a1-b2,a2-b1,而dist(a1,b1)+dist(a2,b2) 肯定比前面交叉的小,画个四边形就很清楚了,那么负数就是大了,也就是说交叉的在我们设计的负权那里是小的,所以就是最佳,也就是不可能有交叉的。

这样分析清楚了之后,就只要直接套用KM就OK了!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
const int maxn=1e2+50;
const double inf=999999999999999.;
const double eps=1e-5;
struct node{
double x,y;
}white[150],black[150];
double g[150][150];
int nx,ny;
bool visx[maxn],visy[maxn];
double slack[maxn];
int linker[maxn];
double lx[maxn],ly[maxn];
bool dfs(int x){
visx[x]=true;
for(int y=0;y<ny;y++){
if(visy[y])continue;
double tmp=lx[x]+ly[y]-g[x][y];
if(fabs(tmp)<eps){
visy[y]=true;
if(linker[y]==-1||dfs(linker[y])){
linker[y]=x;return true;
}
}
else if(slack[y]>tmp)slack[y]=tmp;
}
return false;
}
int KM(){
memset(linker,-1,sizeof(linker));
memset(ly,0,sizeof(ly));
for(int i=0;i<nx;i++){
lx[i]=-inf;
for(int j=0;j<ny;j++){
if(g[i][j]>lx[i])lx[i]=g[i][j];
}
}
for(int x=0;x<nx;x++){
for(int i=0;i<ny;i++)slack[i]=inf;
while(true){
memset(visx,false,sizeof(visx));
memset(visy,false,sizeof(visy));
if(dfs(x))break;
double d=inf;
for(int i=0;i<ny;i++)
if(!visy[i]&&d>slack[i])d=slack[i];
for(int i=0;i<nx;i++)
if(visx[i])lx[i]-=d;
for(int i=0;i<ny;i++)
if(visy[i])ly[i]+=d;
else slack[i]-=d;
}
}
int res=0;
for(int i=0;i<ny;i++)
if(linker[i]!=-1)res+=g[linker[i]][i];
return res;
} double dis(node a,node b){
return double(sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)));
}
int n;
int main()
{
std::ios::sync_with_stdio(false);
std::cin.tie(0);
std::cout.tie(0);
int first=0;
while(cin>>n){
nx=ny=n;
if(first)cout<<endl;
first=1;
for(int i=0;i<n;i++){
cin>>white[i].x>>white[i].y;
}
for(int i=0;i<n;i++){
cin>>black[i].x>>black[i].y;
}
for(int i=0;i<n;i++)for(int j=0;j<n;j++)g[i][j]=-dis(black[i],white[j]);
KM();
for(int i=0;i<n;i++)cout<<linker[i]+1<<endl;
}
return 0;
}