@跳跳棋@
@题目描述@
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~@
就是这样,新的一天里,也请多多关照哦(ノω<。)ノ))☆.。~