POJ - 3565 Ants(二分图最小权匹配+KM+思维)

160 篇文章 3 订阅

题目链接:点击查看

题目大意:给出n个蚂蚁和n个苹果树的坐标,我们需要求出每个蚂蚁平时觅食所要去的苹果树,必须保证所有路径不能有交叉

题目分析:因为所有的边不能有交叉,所以我们选择距离最短的两个点匹配即可,然后n个蚂蚁匹配n个苹果树,又是一个完备匹配问题,那么这个题目就转换成了一个最小权匹配的问题了,直接套模板就行了吗?不,其实还有个很严谨的证明,一开始我是为了省精度,用了距离的平方建边,一直WA,后来改成double类型的距离建边,也就是开了个sqrt然后就A了,一直想不明白为什么,看了大佬的证明后就突然有点小理解了:

这道题能用KM在于四边形不等式的几何形式dist (A,B)+dist(C,D)>dist(A,C)+dist(B,D),其中ABCD是四边形,证明利用对角线交点O与AB,CD两条边构成的三角形不等式证明,即OA+OB>AB,OC+OD>CD,两式相加得到AC+BD>AB+CD,对于平方,没有三角形不等式,事实上可以构造使AB*AB+CD*CD=AC*AC+BD*BD

大概就是这样,求边权的时候需要用实际距离,然后因为涉及到了double类型,就要开一个eps保持精度,1e-6足够

代码:

#include<iostream>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<climits>
#include<cmath>
#include<cctype>
#include<stack>
#include<queue>
#include<list>
#include<vector>
#include<set>
#include<map>
#include<sstream>
using namespace std;
 
typedef long long LL;
 
const double inf=1e10;

const double eps=1e-6;
 
const int N=110;
 
int n;

struct Pos
{
	int x,y;
}ant[N],tree[N];

double dis(Pos& a,Pos& b)
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
 
double la[N],lb[N];//顶标
 
bool visa[N],visb[N];//访问标记
 
double maze[N][N];//边权
 
int match[N];//右部点匹配了哪一个左部点
 
double upd[N];
 
bool dfs(int x)
{
	visa[x]=true;//访问标记:x在交错树中
	for(int i=1;i<=n;i++)
	{
		if(!visb[i])
		{
			if(la[x]+lb[i]-maze[x][i]<eps)//相等子图
			{
				visb[i]=true;//访问标记:y在交错树中
				if(!match[i]||dfs(match[i]))
				{
					match[i]=x;
					return true;
				}
			}
			else
				upd[i]=min(upd[i],la[x]+lb[i]-maze[x][i]);
		}
	}
	return false;
} 
 
int KM()
{
	memset(match,0,sizeof(match));
	for(int i=1;i<=n;i++)
	{
		la[i]=-inf;
		lb[i]=0;
		for(int j=1;j<=n;j++)
			la[i]=max(la[i],maze[i][j]);
	}
	for(int i=1;i<=n;i++)
	{
		while(1)//直到左部点找到匹配
		{
			memset(visa,false,sizeof(visa));
			memset(visb,false,sizeof(visb));
			for(int i=1;i<=n;i++)
				upd[i]=inf;
			if(dfs(i))
				break;
			double delta=inf;
			for(int j=1;j<=n;j++)
				if(!visb[j])
					delta=min(delta,upd[j]);
			for(int j=1;j<=n;j++)//修改顶标
			{
				if(visa[j])
					la[j]-=delta;
				if(visb[j])
					lb[j]+=delta;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		ans+=maze[match[i]][i];
	return ans;
}
 
int main()
{
//  freopen("input.txt","r",stdin);
//	ios::sync_with_stdio(false);
	while(scanf("%d",&n)!=EOF)
	{
		for(int i=1;i<=n;i++)
			scanf("%d%d",&ant[i].x,&ant[i].y);
		for(int i=1;i<=n;i++)
			scanf("%d%d",&tree[i].x,&tree[i].y);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				maze[i][j]=-dis(tree[i],ant[j]);
		KM();
		for(int i=1;i<=n;i++)
			printf("%d\n",match[i]);
	} 
	
	
	
	
	
	
     
     
     
     
      
     
    return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frozen_Guardian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值