UVALive 4043 Ants(二分图完美匹配)

时间:2021-05-07 22:33:05

题意:每个蚁群有自己的食物源(苹果树),已知蚂蚁靠气味辨别行进方向,所以蚁群之间的行动轨迹不能重叠。现在给出坐标系中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)