日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【BZOJ - 2144】跳跳棋

發布時間:2024/1/18 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【BZOJ - 2144】跳跳棋 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

@跳跳棋@

  • @跳跳棋@
    • @題目描述@
    • @分析1@
    • @分析2@
    • @代碼實現@
    • @END~@


@題目描述@

Description
跳跳棋是在一條數軸上進行的。棋子只能擺在整點上。每個點不能擺超過一個棋子。我們用跳跳棋來做一個簡單的游戲:棋盤上有3顆棋子,分別在a,b,c這三個位置。我們要通過最少的跳動把他們的位置移動成x,y,z。(棋子是沒有區別的)跳動的規則很簡單,任意選一顆棋子,對一顆中軸棋子跳動。跳動后兩顆棋子距離不變。一次只允許跳過1顆棋子。

寫一個程序,首先判斷是否可以完成任務。如果可以,輸出最少需要的跳動次數。

Input
第一行包含三個整數,表示當前棋子的位置a b c。(互不相同)
第二行包含三個整數,表示目標位置x y z。(互不相同)
Output
如果無解,輸出一行NO。如果可以到達,第一行輸出YES,第二行輸出最少步數。

Sample Input
1 2 3
0 3 5
Sample Output
YES
2

【范圍】
100% 絕對值不超過10^9

@分析1@

這道題真的很跳……

首先這樣的題如果用絕對坐標(a, b, c)來做絕對不好做,所以考慮使用相對距離來表示點。對于三元組(a, b, c)將它表示為(l, r, m) = (b-a, c-b, m),含義為:(左邊點離中間點的距離,右邊點離中間點的距離,中間點坐標)。

然后對于題目所提到的操作將操作拆為兩種:邊緣點往中間跳,中間點往兩邊跳。
當r>l時,左邊點可以往中間跳,從(l, r, m)轉移到了(l, r-l, m+l)
當l>r時,右邊點可以往中間跳,從(l, r, m)轉移到了(l-r, r, m-r)
中間點往兩邊跳沒有附加條件,從(l, r, m)可以轉移到(l, r+l, m-l)與(l+r, r, m+r)
這樣就可以建圖,問題轉化為求圖上兩點最短距離。

但是……這還是不夠??紤]到中間往兩邊跳是兩邊往中間跳的逆操作,我們可以給邊定向,即只考慮兩邊往中間跳的操作。然后我們發現,對于某一個狀態(l, r, m),通過兩邊往中間跳的操作后,l和r不會變得更大,所以這是一個有向無環圖。更進一步地,當l < r或l > r時,狀態有唯一轉移,否則狀態無轉移。聯想到有根樹上除根節點外每個節點只有一個父親,根節點沒有父親。所以這其實是一顆樹,且狀態(l, r-l, m+l)或(l-r, r, m-r)是狀態(l, r, m)的父節點,當 l = r 時狀態(l, r, m)是根節點。

問題轉化為求樹上兩點最短距離。
再簡單轉化,就是求樹上兩點的LCA。

@分析2@

分析到這一步已經很不容易,但是還還還不夠……
考慮到我們一般做LCA的方法:倍增法。先 O(log2n) O ( l o g 2 n ) 的時間將兩個點跳到相同深度,再一級一級地同時往上跳直到兩個點相同。但是倍增法我們需要 O(n) O ( n ) 的dfs預處理,所以此處并不適用。

再觀察一下這個轉移的定義:

當r>l時,左邊點可以往中間跳,從(l, r, m)轉移到了(l, r-l, m+l)
當l>r時,右邊點可以往中間跳,從(l, r, m)轉移到了(l-r, r, m-r)

再聯想一下gcd的減法形式gcd(x, y) = gcd(y, x-y) = gcd(x-y, y) ( x > y )

是不是有點像?對于某一個狀態,假設 l < r,則(l, r-l, m+l),(l, r-2l, m+2l),…,(l, r%l, m+r/l*l)都是狀態(l, r, m)的祖先。但是!與gcd算法不同的是,如果r%l=0的話則不能跳到最上面的節點,因為在最后的時候 l 將會等于 r。

因為gcd的復雜度可以證明得到不超過 log(n) l o g ( n ) ,所以我們就有了一個代替倍增的玩意兒。
具體來說,我們先求出兩個點的深度,以及兩個點所在樹的根節點。比較根節點判斷是否有解。
【為了簡稱,我們稱(l, r, m)到達(l, r%l, m+r/l*l)為“大步跳”】【作者太Lazy了233】
然后,對于深度較大的節點,假如它通過“大步跳”到達的節點深度比另外一個節點大,則它就直接跳到深度相同的地方;否則就跳到“大步跳”到達的節點,并繼續迭代。
再然后,取兩個點“大步跳”到達的點中深度較大的點,假如說在這個點之前它們就已經相遇了,就直接跳到相遇的點作為它們兩個點的LCA;否則就同時往上跳到深度較大的點的深度,并繼續迭代。

@代碼實現@

非常丑陋……不建議參考。
如果還有什么不懂的地方就評論在下面吧,作者會盡力解疑的。
【這道題太毒瘤啦……建圖卡死人,LCA卡死人,竟然冒出來個gcd……】

#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; inline ll min(ll a, ll b) {return a < b ? a : b; } ll rootl, rootr, rootm; ll dep(ll l, ll r, ll m) {if( l == 0 || r == 0 ) {rootl = l, rootr = r, rootm = m;if( l == 0 ) rootl = r, rootm += r;if( r == 0 ) rootr = l, rootm -= l;return 0;}if( r > l )return dep(l, r%l, m+r/l*l) + r/l;else return dep(l%r, r, m-l/r*r) + l/r; } void sort(ll &a, ll &b, ll &c) {if( a > b ) swap(a, b);if( b > c ) swap(b, c);if( a > b ) swap(a, b); } int main() {ll a, b, c, x, y, z;scanf("%lld%lld%lld%lld%lld%lld", &a, &b, &c, &x, &y, &z);sort(a, b, c), sort(x, y, z);ll l1 = b-a, r1 = c-b, m1 = b;ll l2 = y-x, r2 = z-y, m2 = y;ll d1 = dep(l1, r1, m1);ll rl = rootl, rr = rootr, rm = rootm;ll d2 = dep(l2, r2, m2);if( rootl != rl || rootr != rr || rootm != rm ) {puts("NO");return 0;}else puts("YES");ll ans = 0;while( d1 != d2 ) {if( d1 > d2 ) {if( r1 > l1 ) {ll k = min(r1/l1, d1-d2);d1 -= k, ans += k;l1 = l1, m1 = m1 + k*l1, r1 = r1 - k*l1;}else {ll k = min(l1/r1, d1-d2);d1 -= k, ans += k;r1 = r1, m1 = m1 - k*r1, l1 = l1 - k*r1;}}else {if( r2 > l2 ) {ll k = min(r2/l2, d2-d1);d2 -= k, ans += k;l2 = l2, m2 = m2 + k*l2, r2 = r2 - k*l2;}else {ll k = min(l2/r2, d2-d1);d2 -= k, ans += k;r2 = r2, m2 = m2 - k*r2, l2 = l2 - k*r2;}}}while( l1 != l2 || r1 != r2 || m1 != m2 ) {ll k;if( r1 > l1 ) {if( r2 > l2 ) {if( r1/l1 > r2/l2 )k = r2/l2;else k = r1/l1;d1 -= k, d2 -= k, ans += 2*k;l1 = l1, m1 = m1 + k*l1, r1 = r1 - k*l1;l2 = l2, m2 = m2 + k*l2, r2 = r2 - k*l2;}else {if( r1/l1 > l2/r2 ) {if( l2 % r2 == 0 )k = (m2-m1)/(r2+l1);else k = l2/r2; /* 如果兩個點能跳到同一節點,則:l1=l2-k*r2,r2=r1-k*l1,m1+k*l1=m2-k*r2 解方程可得k,下面同理 */}else {if( r1 % l1 == 0 )k = (m2-m1)/(r2+l1);else k = r1/l1;}d1 -= k, d2 -= k, ans += 2*k;l1 = l1, m1 = m1 + k*l1, r1 = r1 - k*l1;r2 = r2, m2 = m2 - k*r2, l2 = l2 - k*r2;}}else {if( r2 > l2 ) {if( l1/r1 > r2/l2 ) {if( r2 % l2 == 0 ) k = (m1-m2)/(r1+l2);else k = r2/l2;}else {if( l1 % r1 == 0 )k = (m1-m2)/(r1+l2);else k = l1/r1;}d1 -= k, d2 -= k, ans += 2*k;r1 = r1, m1 = m1 - k*r1, l1 = l1 - k*r1;l2 = l2, m2 = m2 + k*l2, r2 = r2 - k*l2;}else {if( l1/r1 > l2/r2 )k = l2/r2;else k = l1/r1;d1 -= k, d2 -= k, ans += 2*k;r1 = r1, m1 = m1 - k*r1, l1 = l1 - k*r1;r2 = r2, m2 = m2 - k*r2, l2 = l2 - k*r2;}}}printf("%lld\n", ans); }

@END~@

就是這樣,新的一天里,也請多多關照哦(ノω<。)ノ))☆.。~

總結

以上是生活随笔為你收集整理的【BZOJ - 2144】跳跳棋的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。