【BZOJ - 2144】跳跳棋

@跳跳棋@


@题目描述@

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~@

就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值