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=
, b=
, c=
那麼此時我們一步一步跳就會耗費大量的時間。
次數 | 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,就是
,跳的步數為
有點像輾轉相除?那麼時間應該是
。
好了,我們已經可以快速跳動了,那麼判斷深度就非常容易。只需一直跳到根節點,看看跳動的次數就好了。
同時如果最後跳到的根節點不同,說明不在通一棵樹上。
還剩之後一件事。究竟跳了幾步相遇?我們可以繼續借鑑 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;
}
因為我們跳動的棋子只能越過一個棋子,也就是它的對稱軸(上的點),那麼對於(a,b,c)只有 b - a>c - a 時,a 才能跳動。 ↩︎