1. 程式人生 > >bzoj-2144 跳跳棋

bzoj-2144 跳跳棋

2144: 跳跳棋
題目連結

時間限制: 10 Sec 記憶體限制: 259 MB

題目描述

跳跳棋是在一條數軸上進行的。棋子只能擺在整點上。每個點不能擺超過一個棋子。我們用跳跳棋來做一
個簡單的遊戲:棋盤上有3顆棋子,分別在a,b,c這三個位置。我們要通過最少的跳動把他們的位置移動
成x,y,z。(棋子是沒有區別的)跳動的規則很簡單,任意選一顆棋子,對一顆中軸棋子跳動。跳動後兩
顆棋子距離不變。一次只允許跳過1顆棋子。 寫一個程式,首先判斷是否可以完成任務。如果可以,輸出
最少需要的跳動次數。

輸入

第一行包含三個整數,表示當前棋子的位置a b c。(互不相同)第二行包含三個整數,表示目標位置x y z。
(互不相同)

輸出

如果無解,輸出一行NO。如果可以到達,第一行輸出YES,第二行輸出最少步數。

樣例輸入

1 2 3
0 3 5

樣例輸出

YES
2

【範圍】

100% 絕對值不超過10^9

題解
看起來沒什麼想法。
暴瘦顯然沒有希望。

我們理一理過程,看看能發現什麼。

拿到一個三元組(a,b,c),其中 a<b<c。我們總共有 4 種跳法。
a 向右跳,b 向左或右跳,c 向左跳。
舉過幾個例子會發現,其中 a 向右跳和 c 向左跳不能同時滿足。(WHY1

so,沒感覺到有點東西嗎?
如果我們把三元組看成節點,b 跳動後得到的三元組為當前節點的子節點,a 或 c 跳動得到的三元組設為父節點,那麼這就是一棵二叉樹!
一直跳到最後一定存在一個狀態是 b - a = c - b,此時這個節點是根節點。
由此,這道題轉化成了圖論的模型。

樹上兩點只可能有一條連通路徑,所以,我們只需要想辦法來求這個路徑就好了。
還記得LCA演算法嗎?
(不過這裡恐怕不能直接套用,因為我們不能確定圖的規模)
首先我們需要知道無解的情況是怎麼回事?如何連跳?如何把兩點跳到同一深度?

由於它們兩個點不一定在通一棵樹上,導致可能出現無解情況。(也可以證明只有這個可能)

如何連跳?我們依然需要觀察一下,舉幾個例子。
如果 a= 10 10 , b= 15

15 , c= 1 0 9 10^9
那麼此時我們一步一步跳就會耗費大量的時間。

次數 a b c
0 10 15 N
1 15 20 N
2 20 25 N
3 20 30 N
4 25 35 N
x 10+5x 15+5x N

一直到 b c 之間的距離小於等於 5,就是 ( ( c b 1 ) m o d   5 ) + 1 ((c-b-1)mod\ 5)+1 ,跳的步數為 ( c b 1 ) / 5 (c-b-1)/5
有點像輾轉相除?那麼時間應該是 O ( l o g N ) O(logN)

好了,我們已經可以快速跳動了,那麼判斷深度就非常容易。只需一直跳到根節點,看看跳動的次數就好了。
同時如果最後跳到的根節點不同,說明不在通一棵樹上。

還剩之後一件事。究竟跳了幾步相遇?我們可以繼續借鑑 LCA 的想法,利用倍增來求(也可以用二分)。

程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define LL long long
LL a,b,c,x,y,z,a1,b1,c1,x1,y1,z1,ans;
void jump(LL&x,LL&y,LL&z,LL tim)
{
	LL A,B,stp;
	while (y-x!=z-y&&tim)
	{
		A=y-x,B=z-y;
		if (A<B)
		{
			stp=(B+A-1)/A-1;
			stp=min(tim,stp);
			x+=A*stp;
			y+=A*stp;
			tim-=stp;
		}else
		{
			stp=(A+B-1)/B-1;
			stp=min(tim,stp);
			z-=B*stp;
			y-=B*stp;
			tim-=stp;
		}
	}
}
int getstp(LL &x,LL &y,LL &z)
{
	LL A,B,stp,ret=0;
	while (y-x!=z-y)
	{
		A=y-x,B=z-y;
		if (A<B)
		{
			stp=(B+A-1)/A-1;
			x+=A*stp;
			y+=A*stp;
			ret+=stp;
		}else
		{
			stp=(A+B-1)/B-1;
			z-=B*stp;
			y-=B*stp;
			ret+=stp;
		}
	}
	return ret;
}
bool check(LL tim)
{
	x1=x;y1=y;z1=z;a1=a;b1=b;c1=c;
	jump(a1,b1,c1,tim);
	jump(x1,y1,z1,tim);
	return x1==a1&&y1==b1&&z1==c1;
}
int main()
{
	scanf("%lld%lld%lld",&x1,&y1,&z1);
	scanf("%lld%lld%lld",&a1,&b1,&c1);
	if (x1>y1) swap(x1,y1);
	if (y1>z1) swap(y1,z1);
	if (x1>y1) swap(x1,y1);
	if (a1>b1) swap(a1,b1);
	if (b1>c1) swap(b1,c1);
	if (a1>b1) swap(a1,b1);
	x=x1;y=y1;z=z1;a=a1;b=b1;c=c1;
	int L1=getstp(x1,y1,z1),L2=getstp(a1,b1,c1);
	if (x1!=a1||y1!=b1||z1!=c1) {printf("NO\n");return 0;}
	printf("YES\n");
	ans=0;
	if (L1>L2) ans=L1-L2,jump(x,y,z,ans);else
	if (L1<L2) ans=L2-L1,jump(a,b,c,ans);
	LL Le=0,Ri=min(L1,L2)+1,mid;
	while (Le<=Ri)
	{
		mid=(Ri-Le>>1)+Le;
		if (check(mid)) Ri=mid-1;
		else Le=mid+1;
	}
	printf("%lld\n",ans+Le*2);
	return 0;
}

  1. 因為我們跳動的棋子只能越過一個棋子,也就是它的對稱軸(上的點),那麼對於(a,b,c)只有 b - a>c - a 時,a 才能跳動。 ↩︎