【BZOJ2144】跳跳棋-二分+LCA

測試地址: 跳跳棋
做法: 本題需要用到二分+LCA。
一道神題。注意到三個棋子可以進行以下的跳躍:
1.中間的棋子跳過兩邊的棋子向外跳躍;
2.距離中間棋子較近的那個棋子跳過中間的妻子向內跳躍。
因爲限制了只能跳過一個棋子,所以上面的兩種跳躍方式就是全部了。而我們發現,除非兩邊的棋子和中間的棋子距離相等,通過第二種跳躍都只能到達一種狀態,而第一種跳躍和第二種跳躍是互逆的操作,於是我們驚奇地發現:第一種跳躍可以看成從一點走到兩個兒子,第二種跳躍可以看成跳到父親,那麼所有的狀態就會形成一些二叉樹。於是問題就變成了求兩個狀態的LCA,以及它們到那個LCA的步數之和。
首先,我們應該判斷兩個狀態可不可以互達。要做到這一點,實際上就是看兩個狀態所在樹的根是不是相同就行了。怎麼樣算出這個根呢?我們發現,令ba=d1,cb=d2b-a=d_1,c-b=d_2,不妨設d1<d2d_1<d_2(否則對稱處理即可),那麼a,ba,b兩點可以一直向右,每次移動d1d_1的距離,直到d2kd1d1d_2-k\cdot d_1\le d_1爲止。於是我們發現,這幾乎就是一個取模運算,除了當d1d2d_1 | d_2時,最後要剩下d1d_1的距離,移動d2d11\frac{d2}{d1}-1步,否則就剩下d2%d1d_2\% d_1的距離,移動d2d1\lfloor \frac{d2}{d1}\rfloor步。而根據與其類似的輾轉相除的過程的複雜度,這樣計算的複雜度是O(logd)O(\log d)的,這樣就能很快算出根了。
接下來就是求LCA的問題。回顧往常求LCA的思路,我們先把深度較深的點提升到和另一個點深度齊平,然後一起在樹上跳。但在這裏由於狀態數很多,不可能用倍增處理,所以我們直接二分LCA的深度即可,對於每個midmid求出兩點深度在midmid的祖先,複雜度和上面的算法一樣是O(logd)O(\log d)的。這樣我們就得到了一個複雜度爲O(log2d)O(\log^2 d)的算法,可以輕易地通過此題。
以下是本人代碼:

#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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章