【BZOJ2144】跳跳棋-二分+LCA
阿新 • • 發佈:2019-02-11
測試地址: 跳跳棋
做法: 本題需要用到二分+LCA。
一道神題。注意到三個棋子可以進行以下的跳躍:
1.中間的棋子跳過兩邊的棋子向外跳躍;
2.距離中間棋子較近的那個棋子跳過中間的妻子向內跳躍。
因為限制了只能跳過一個棋子,所以上面的兩種跳躍方式就是全部了。而我們發現,除非兩邊的棋子和中間的棋子距離相等,通過第二種跳躍都只能到達一種狀態,而第一種跳躍和第二種跳躍是互逆的操作,於是我們驚奇地發現:第一種跳躍可以看成從一點走到兩個兒子,第二種跳躍可以看成跳到父親,那麼所有的狀態就會形成一些二叉樹。於是問題就變成了求兩個狀態的LCA,以及它們到那個LCA的步數之和。
首先,我們應該判斷兩個狀態可不可以互達。要做到這一點,實際上就是看兩個狀態所在樹的根是不是相同就行了。怎麼樣算出這個根呢?我們發現,令,不妨設(否則對稱處理即可),那麼兩點可以一直向右,每次移動的距離,直到為止。於是我們發現,這幾乎就是一個取模運算,除了當時,最後要剩下的距離,移動步,否則就剩下的距離,移動步。而根據與其類似的輾轉相除的過程的複雜度,這樣計算的複雜度是的,這樣就能很快算出根了。
接下來就是求LCA的問題。回顧往常求LCA的思路,我們先把深度較深的點提升到和另一個點深度齊平,然後一起在樹上跳。但在這裡由於狀態數很多,不可能用倍增處理,所以我們直接二分LCA的深度即可,對於每個求出兩點深度在的祖先,複雜度和上面的演算法一樣是的。這樣我們就得到了一個複雜度為的演算法,可以輕易地通過此題。
以下是本人程式碼:
#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;
}