测试地址: 跳跳棋
做法: 本题需要用到二分+LCA。
一道神题。注意到三个棋子可以进行以下的跳跃:
1.中间的棋子跳过两边的棋子向外跳跃;
2.距离中间棋子较近的那个棋子跳过中间的妻子向内跳跃。
因为限制了只能跳过一个棋子,所以上面的两种跳跃方式就是全部了。而我们发现,除非两边的棋子和中间的棋子距离相等,通过第二种跳跃都只能到达一种状态,而第一种跳跃和第二种跳跃是互逆的操作,于是我们惊奇地发现:第一种跳跃可以看成从一点走到两个儿子,第二种跳跃可以看成跳到父亲,那么所有的状态就会形成一些二叉树。于是问题就变成了求两个状态的LCA,以及它们到那个LCA的步数之和。
首先,我们应该判断两个状态可不可以互达。要做到这一点,实际上就是看两个状态所在树的根是不是相同就行了。怎么样算出这个根呢?我们发现,令
b
−
a
=
d
1
,
c
−
b
=
d
2
b-a=d_1,c-b=d_2
b−a=d1,c−b=d2,不妨设
d
1
<
d
2
d_1<d_2
d1<d2(否则对称处理即可),那么
a
,
b
a,b
a,b两点可以一直向右,每次移动
d
1
d_1
d1的距离,直到
d
2
−
k
⋅
d
1
≤
d
1
d_2-k\cdot d_1\le d_1
d2−k⋅d1≤d1为止。于是我们发现,这几乎就是一个取模运算,除了当
d
1
∣
d
2
d_1 | d_2
d1∣d2时,最后要剩下
d
1
d_1
d1的距离,移动
d
2
d
1
−
1
\frac{d2}{d1}-1
d1d2−1步,否则就剩下
d
2
%
d
1
d_2\% d_1
d2%d1的距离,移动
⌊
d
2
d
1
⌋
\lfloor \frac{d2}{d1}\rfloor
⌊d1d2⌋步。而根据与其类似的辗转相除的过程的复杂度,这样计算的复杂度是
O
(
log
d
)
O(\log d)
O(logd)的,这样就能很快算出根了。
接下来就是求LCA的问题。回顾往常求LCA的思路,我们先把深度较深的点提升到和另一个点深度齐平,然后一起在树上跳。但在这里由于状态数很多,不可能用倍增处理,所以我们直接二分LCA的深度即可,对于每个
m
i
d
mid
mid求出两点深度在
m
i
d
mid
mid的祖先,复杂度和上面的算法一样是
O
(
log
d
)
O(\log d)
O(logd)的。这样我们就得到了一个复杂度为
O
(
log
2
d
)
O(\log^2 d)
O(log2d)的算法,可以轻易地通过此题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf=1000000000ll*1000000000ll;
ll a,b,c,x,y,z,f;
ll finala,finalb,finalc,finalx,finaly,finalz;
ll dis0,dis1;
void find(ll a,ll b,ll c,ll limit,ll &finala,ll &finalb,ll &finalc,ll &dis)
{
if (b-a==c-b) return;
if (b-a<c-b)
{
ll nxt=min((c-b)/(b-a)-((c-b)%(b-a)==0),limit-dis);
dis+=nxt;
nxt=nxt*(b-a);
a+=nxt,b+=nxt;
}
else
{
ll nxt=min((b-a)/(c-b)-((b-a)%(c-b)==0),limit-dis);
dis+=nxt;
nxt=nxt*(c-b);
b-=nxt,c-=nxt;
}
finala=a,finalb=b,finalc=c;
if (dis==limit) return;
find(a,b,c,limit,finala,finalb,finalc,dis);
}
int main()
{
scanf("%lld%lld%lld",&a,&b,&c);
if (a>b) swap(a,b);
if (a>c) swap(a,c);
if (b>c) swap(b,c);
scanf("%lld%lld%lld",&x,&y,&z);
if (x>y) swap(x,y);
if (x>z) swap(x,z);
if (y>z) swap(y,z);
finala=a,finalb=b,finalc=c;
find(a,b,c,inf,finala,finalb,finalc,dis0);
finalx=x,finaly=y,finalz=z;
find(x,y,z,inf,finalx,finaly,finalz,dis1);
if (finala!=finalx||finalb!=finaly||finalc!=finalz)
{
printf("NO");
return 0;
}
if (dis0<dis1)
{
swap(dis0,dis1);
swap(a,x);
swap(b,y);
swap(c,z);
}
find(a,b,c,dis0-dis1,finala,finalb,finalc,f=0);
a=finala,b=finalb,c=finalc;
ll l=0,r=dis1;
while(l<r)
{
ll mid=(l+r)>>1;
find(a,b,c,mid,finala,finalb,finalc,f=0);
find(x,y,z,mid,finalx,finaly,finalz,f=0);
if (finala==finalx&&finalb==finaly&&finalc==finalz) r=mid;
else l=mid+1;
}
printf("YES\n%lld",dis0-dis1+(l<<1));
return 0;
}