题意:每个蚁群有自己的食物源(苹果树),已知蚂蚁靠气味辨别行进方向,所以蚁群之间的行动轨迹不能重叠。现在给出坐标系中n个蚁群和n棵果树的坐标,两两配对,实现以上要求。输出的第 i 行表示第 i 个蚁群应该去哪棵果树。(已知2*n个点互不重合)

容易想到二分完美匹配,但究竟以什么为权值?需要利用一个关系:两条线段如果相交,那么线段长度之和必然大于其四个点不相交的连法对应的线段长度之和。(利用三角不等式可以证明)。

如此,求出每个蚁群到每棵果树的曼哈顿距离,只要保证每条匹配边的长度最短,即长度之和最小,就可以得到不重叠的方案。

注意:

  1、通常意义上,二分图完美匹配求的是权值和最大的方案(初始的可行顶标保证大于等于任何一条边,逐渐向下调整,得到的结果必然是最大值)。这里求最小值,只需取曼哈顿距离的负值即可。

  2、left[]数组中保存的是右侧(T侧、奇点)一点所匹配的点,即 left[v]。这里要求根据蚁群的升序输出果树编号,所以应该把蚁群保存在右侧。

           S[]         T[]  

    可行顶标:  Lx[]        Ly[]

    数据:    apple        ant

    匹配:              left[]

    松弛:              slack[]

    !!注意:S、T点数不同时,初始化left[]对应的点是T集合的点数

  3、松弛操作:

    匈牙利算法(KM)的步骤:一、根据每个偶点(左侧),检查match()函数,O(n2);二、若ture,检查下一个偶点,否则,用update()函数调整可行顶标。

    一般情况下,对于每一条一端在S集合(偶点),另一端在T’集合(还未被标记)的线段,都有可能被增广。查找的时间复杂度为O(n2),比较的时间复杂度为O(1),所以update()的时间复杂度为O(n2)。整个KM算法的时间复杂度为O(n4)。

    这种算法本身就利用了“松弛”的思想。松弛,即用更优解替代次解,且不可逆。通过update()找到最小值,使新的“等边”加入到“交错树”中,同时不影响已存在于交错树中的边。(为何取最小值?太小,没有新边加入;太大,得不到最大权值之和。)这里注意,每次调整,交错树上的偶点总比奇点多1,通过偶点可行顶标-a,奇点+a,使得树上的顶标和逐渐自上而下的逼近完美匹配的最大值。

    优化算法,即对于update()函数优化。在match()中,当找到每一个未标记的点(!T[v]),且不是等边(Lx+Ly!=gap[x][y]),就记录下这条边的值:slack[v]=min(Lx+Ly-gap[x][y])。那么在update()调整时,只需要遍历一遍T’集合(未标记),就可以找到需要调整的最小值了。如此一来,update()的时间复杂度为O(n),match()中增加的操作不影响其复杂度。整个KM算法的时间复杂度为O(n3)。

  4、注意“交错树”的树形结构。

    一、left[]在右侧的原因。每条边都是从左侧(偶点)出发,至右侧(奇点)结束的(这是增广路算法的核心)。每次选取一个S'集合(未标记)的点为树根,若沿等边找到一个T'集合的点,则找到一条增广路;否则,就是找到了T集合(已标记、以匹配)的点,沿匹配边v->left[v] 回到左侧(偶点)。重复操作,直到找到增广路;否则,调整可行顶标。

    二、slack[]在右侧的原因。如3中解释,每次只需找T'集合的点,取最小值即可。

以上是学习二分匹配的一点心得,还望指正。

  5、时间复杂度(补)

  之前所说的O(n3)其实是不准确的。实际上在KM算法中,对于左侧的每个点要做一遍match();match()要沿等边检查右侧的点,直到出现未匹配点或不存在这样的点为止;若不存在这样的点,需要做update(),其中要对右侧点检查slack[]找最小值,之后要调整全部的可行顶标。若令左侧有S个点,右侧有T个点,那么总复杂度是S*T*(T+(S+T))),当S=T=n,时间复杂度即为之前所说的O(n3)。但是为了更好的描述,我们保证S<=T,那么时间复杂度为O(ST2)。

  在建模时,往往需要拆点,T很可能就是n*m级的(又或是n2,m2级的),那么时间复杂度就可能是O(m2n3)级(又或是O(n5),O(nm4))。考虑二分匹配的时限一般在3~9s,一般也就是107~108的计算量,n,m<100时可以考虑,如果n,m的数据范围再大一些,或拆点后T的数量更大一些(比如T=nm2),都是可以直接pass掉的。e.g:我在这之后写的一篇博客中也讨论到这个问题http://www.cnblogs.com/zstu-abc/p/3293542.html

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define clr(a,m) memset(a,m,sizeof(a))
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std; const int MAXN=;
const int INF =1e9;
const double eps=1e-; struct Point{
double x,y;
}ant[MAXN],apple[MAXN]; double gap[MAXN][MAXN];
double Lx[MAXN],Ly[MAXN];
int left[MAXN];
bool S[MAXN],T[MAXN]; void read(int n)
{
rep(i,,n)
scanf("%lf%lf",&ant[i].x,&ant[i].y);
rep(i,,n)
scanf("%lf%lf",&apple[i].x,&apple[i].y);
rep(i,,n){
double x1=apple[i].x,y1=apple[i].y;
rep(j,,n){
double x2=ant[j].x,y2=ant[j].y;
gap[i][j]=-sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
}
} bool match(int i,int n)
{
S[i]=true;
rep(j,,n)
if(fabs(Lx[i]+Ly[j]-gap[i][j])<eps&&!T[j]){
T[j]=true;
if(!left[j]||match(left[j],n)){
left[j]=i;
return true;
}
}
return false;
} void update(int n)
{
double a=INF;
rep(i,,n)
if(S[i])
rep(j,,n)
if(!T[j])
a=min(a,Lx[i]+Ly[j]-gap[i][j]);
rep(i,,n){
if(S[i])Lx[i]-=a;
if(T[i])Ly[i]+=a;
}
} void KM(int n)
{
rep(i,,n){
left[i]=Ly[i]=;
Lx[i]=-INF;
rep(j,,n)
Lx[i]=max(Lx[i],gap[i][j]);
}
rep(i,,n)
while()
{
rep(j,,n)
S[j]=T[j]=;
if(match(i,n))
break;
else
update(n);
}
} void print(int n)
{
rep(i,,n)
printf("%d\n",left[i]);
} int main()
{
int n,cnt=;
while(~scanf("%d",&n))
{
if(cnt++)
puts("");
read(n);
KM(n);
print(n);
}
return ;
}

O(n4)

 #include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define clr(a,m) memset(a,m,sizeof(a))
#define rep(i,a,b) for(int i=a;i<=b;i++)
using namespace std; const int MAXN=;
const int INF =1e9;
const double eps=1e-; struct Point{
double x,y;
}ant[MAXN],apple[MAXN]; double gap[MAXN][MAXN];
double Lx[MAXN],Ly[MAXN],slack[MAXN];
int left[MAXN],n;
bool S[MAXN],T[MAXN]; void read()
{
rep(i,,n)
scanf("%lf%lf",&ant[i].x,&ant[i].y);
rep(i,,n)
scanf("%lf%lf",&apple[i].x,&apple[i].y);
rep(u,,n){
double x1=apple[u].x,y1=apple[u].y;
rep(v,,n){
double x2=ant[v].x,y2=ant[v].y;
gap[u][v]=-sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
}
} bool match(int u)
{
S[u]=true;
rep(v,,n)
if(!T[v]){
double tmp=Lx[u]+Ly[v]-gap[u][v];//原本写的是fabs直接在这里处理,不是很合理,不过也没有错
//tmp不可能是负的,否则意味着Lx+Ly<gap[x][y]
if(fabs(tmp)<eps){
T[v]=true;
if(!left[v]||match(left[v])){
left[v]=u;
return true;
}
}else slack[v]=min(slack[v],tmp);
}
return false;
} void update()
{
double a=INF;
rep(v,,n)
if(!T[v])//不加if会TLE的
a=min(a,slack[v]);
rep(i,,n){
if(S[i])Lx[i]-=a;
if(T[i])Ly[i]+=a;
}
} void KM()
{
rep(i,,n){
left[i]=Ly[i]=;
Lx[i]=-INF;
rep(j,,n)
Lx[i]=max(Lx[i],gap[i][j]);
}
rep(i,,n){
rep(j,,n)
slack[j]=INF;
while()
{
rep(j,,n)
S[j]=T[j]=;
if(match(i))
break;
else
update();
}
}
} void print()
{
rep(v,,n)
printf("%d\n",left[v]);
} int main()
{
int cnt=;
while(~scanf("%d",&n))
{
if(cnt++)
puts("");
read();
KM();
print();
}
return ;
}

O(n3)

UVALive 4043 Ants(二分图完美匹配)的更多相关文章

  1. UVaLive 4043 Ants (最佳完美匹配)

    题意:给定 n 个只蚂蚁和 n 棵树的坐标,问怎么匹配使得每个蚂蚁到树的连线不相交. 析:可以把蚂蚁和树分别看成是两类,那么就是一个完全匹配就好,但是要他们的连线不相交,那么就得考虑,最佳完美匹配是可 ...

  2. UVA 1411 - Ants(二分图完美匹配)

    UVA 1411 - Ants 题目链接 题意:给定一些黑点白点,要求一个黑点连接一个白点,而且全部线段都不相交 思路:二分图完美匹配,权值存负的欧几里得距离,这种话,相交肯定比不相交权值小,所以做一 ...

  3. UVALive 4043 转化最佳完美匹配

    首先黑点和白点是组成一个二分图这毫无疑问 关键是题目中要求的所有黑白配的线不能交叉...一开始我也没想到这个怎么转化为二分图里面的算法. 后来看书才知道,如果两两交叉,则可以把两根线当四边形的对角线, ...

  4. Uvalive 4043 Ants —— 二分图最大权匹配 KM算法

    题目链接:https://vjudge.net/problem/UVALive-4043 题意: 给出n个白点和n个黑点的坐标, 要求用n条不相交的线段把他们连接起来,其中每条线段恰好连接一个白点和黑 ...

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

    layout: post title: 训练指南 UVALive - 4043(二分图匹配 + KM算法) author: "luowentaoaa" catalog: true ...

  6. UVA 11383 - Golden Tiger Claw(二分图完美匹配扩展)

    UVA 11383 - Golden Tiger Claw 题目链接 题意:给定每列和每行的和,给定一个矩阵,要求每一个格子(x, y)的值小于row(i) + col(j),求一种方案,而且全部行列 ...

  7. UVA 10888 - Warehouse(二分图完美匹配)

    UVA 10888 - Warehouse option=com_onlinejudge&Itemid=8&page=show_problem&category=562& ...

  8. Hall定理 二分图完美匹配

    充分性证明就先咕了,因为楼主太弱了,有一部分没看懂 霍尔定理内容 二分图G中的两部分顶点组成的集合分别为X, Y(假设有\(\lvert X \rvert \leq \lvert Y \rvert\) ...

  9. UVA 11383 Golden Tiger Claw(最佳二分图完美匹配)

    题意:在一个N*N的方格中,各有一个整数w(i,j),现在要求给每行构造row(i),给每列构造col(j),使得任意w(i,j)<=row(i)+col(j),输出row(i)与col(j)之 ...

随机推荐

  1. angular入门系列教程3

    主题: 本篇主要目的就是继续完善home页,增加tab导航的三个页index index1 index2 效果图: 细节: 初始化的JS就是咱们的home.js,仔细来看. angular的route ...

  2. IEnumerator/IEnumerable接口

    IEnumberator函数成员 Current返回序列中当前位置项的 属性 只读属性 返回object类型 MoveNext把枚举器位置前进到集合中下一项的方法 新位置有效返回true,否则fals ...

  3. mvc 分页js

    <script type="text/javascript">     var configA = {         options: {             m ...

  4. linux进程管理之开机启动

    下面用自启动apache为例;自启动脚本:/usr/local/apache2/bin:./apachectl start文件位于/etc/rc.d/init.d下,名为apached, 注意要可执行 ...

  5. js 判断是否为chrome浏览器

    var isChrome =navigator.userAgent.indexOf("Chrome") !== -1 用 navigator.appVersion 不好使,因为al ...

  6. java web项目 。classpath 文件解析

    eclipse工程中.classpath文件含义: 下面是一个.classpath文件内容: < ?xml version="1.0" encoding="UTF- ...

  7. Jmeter 快速入门教程(三-1) --添加响应断言(即loadrunner中所指的检查点)

    [版权所有: whoistester.com & jmeter.cf] 上一节课,我们创建了一个测试场景,并进行了少量vuser的负载测试. 有时候我们执行了测试,但是发现并不是所有事务都执行 ...

  8. Java学习笔记之:Java简介

    一.引言 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称.由James Gosling和同事们共同研发,并在1995年正式推出. ...

  9. python os.stat() 和 stat模块详解

    stat 系统调用时用来返回相关文件的系统状态信息的. 首先我们看一下stat中有哪些属性: >>> import os >>> print os.stat(&qu ...

  10. GuessFist

    import java.util.Scanner; import java.util.Random; /** *跟电脑玩石头剪刀布,需要从控制台输入信息, *然后去判断,然后给予反馈信息 */ pub ...