题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3830
题目大意:在数轴上有三个互不相等的数,对于两个数x和y,x可以移动到它关于y对称的位置x’,当且仅当从x到x’这段区间内没有第三个数。问状态a,b,c能否经过若干次移动变成状态x,y,z。如果能,输出一行YES,第二行输出最小移动次数,否则输出NO。输入有多组数据。
数据范围:a,b,c,x,y,z∈(-10^9,10^9)
题解:首先我们先将a,b,c三个数排序。可以知道,b向两边移动一定是合法的,而如果两边的数向中间移动,只有与b较近的数移动合法,当|a-b|=|b-c|时,则a和c均不能向中间移动。我们发现,这就是一个二叉树的模型,而且是一棵无穷大的二叉树,每个节点有三个参数(a,b,c),当|a-b|=|b-c|时即为树根。题目所求的就是(a,b,c)与(x,y,z)在树上的距离。注意两个点可能在不同的树上,所以要先找到它们的树根,若不同则无解,否则找两个点的公共祖先计算距离。
那么如何找树根呢?暴力肯定是不行的,比如(1,2,999999999),暴力移动时间复杂度将达到10^9,所以我们需要找到更快的方法。
举个例子,(a,b,c)=(1,4,15)。
第一次移动,1→7,(4,7,15);
第二次移动,4→10,(7,10,15);
第三次移动,7→13,(10,13,15);
第四次移动,15→11,(10,11,13);
第五次移动,10→12,(11,12,13)。
观察上面的过程可以发现,一开始|a-b|<|b-c|,所以a和b同时向c的方向移动,直到|a-b|>=|b-c|,然后是b和c同时向a移动,这样交替直到|a-b|=|b-c|。于是我们发现这个过程与辗转相除法相似:当|a-b|<|b-c|时,将a和b同时向右移|b-c|/|a-b|步,每步长|a-b|,反之同理。当|b-c|%|a-b|=0时应少移一步。
这样我们可以在log次以内求出某个点到根的距离。求两个点的最近公共祖先时,先把距离根较远的点上移到与另一点同等的位置,然后二分判断他们到最近公共祖先的距离。至此,题目解决。
时间复杂度O(log^2)
代码如下:
#include <algorithm>
#include <cstdio>
struct P{
int a,b,c;
void Sort()
{
if (a>b) std::swap(a,b);
if (a>c) std::swap(a,c);
if (b>c) std::swap(b,c);
}
int findrt()
{
int d=0;
for (int x,y;b-a!=c-b;)
if (b-a<c-b)
{
x=b-a;
y=(c-b-1)/x;
d+=y;
a+=x*y;
b+=x*y;
}
else
{
x=c-b;
y=(b-a-1)/x;
d+=y;
c-=x*y;
b-=x*y;
}
return d;
}
void up(int d)
{
for (int y,z;d;d-=y)
if (b-a<c-b)
{
z=b-a;
y=(c-b-1)/z;
if (y>d) y=d;
a+=z*y;
b+=z*y;
}
else
{
z=c-b;
y=(b-a-1)/z;
if (y>d) y=d;
c-=z*y;
b-=z*y;
}
}
}A,B,C,D;
bool operator ==(P A,P B){
return A.a==B.a && A.b==B.b && A.c==B.c;
}
int main(){
for (;~scanf("%d%d%d%d%d%d\n",&A.a,&A.b,&A.c,&B.a,&B.b,&B.c);)
{
A.Sort();B.Sort();
C=A;D=B;
int da=A.findrt(),db=B.findrt();
if (!(A==B)) printf("NO\n");
else
{
if (da>db) C.up(da-db);
else D.up(db-da);
int l=0,r=std::min(da,db),mid,ans;
for (;l<=r;)
{
mid=(l+r)>>1;
A=C;B=D;
A.up(mid);B.up(mid);
if (A==B) ans=mid,r=mid-1;
else l=mid+1;
}
printf("YES\n%d\n",2*ans+std::max(da,db)-std::min(da,db));
}
}
}