[题解] 跳跳棋
[题解] 跳跳棋
构造题,好题!
题意
数轴上有三个点,他们之间不可重叠。
一个点只能沿另一个点对称的方向和长度跳跃,且一次能且仅能越过一个点。
每一次上述操作看做一个步骤,给出当前状态和结果状态(\(6\) 个坐标),输出解数。
解题报告
手玩一下这个过程,可以把状态的变换分为两类:
-
两边向中间跳。(比如上图)
-
中间向两边跳。
由于:
一次 能且仅能 越过一个点。
所以对于操作 \(1\) 来说,对于一种确定的状态,只有一边的点可以往中间跳。
不难发现,操作二与操作一 互逆,所以想一想发现这是二叉树的性质。
把操作 \(1\) 看做儿子指向父亲,操作 \(2\) 看做父亲指向儿子。(基于两种操作的性质)
如果要判断有无解,直接查看他们在不在一棵子树中。
但是注意,如何操作才能做到树上的动态问题?
可以手造几组数据试一试,不难发现有很多时候由于间距差过大,一个点可以连续往中间跳,跳不了了(间距小了),还可以从另一边开始跳。
推一下式子,这其实就是辗转相除(减)法的过程,也就是说:
我们构建的这棵树是 抽象出来 的,并非真实存在的,也就无法像普通树上倍增一样处理倍增数组之类的东西。
所以跳跃操作就出来了,可以写迭代的,也可以写递归的。
递归写法:
int getfa(int &a,int &b,int &c){//三个点的状态-->返回操作步数
int A=b-a,B=c-b;
if(A==B)return 0;
//(x,a,b)->(x+a,a,b-a);
if(A<B){
int t=B/A;if(B%A==0)t--;//跳t次
a=a+t*A;b=b+t*A;
return t+getfa(a,b,c);
}
else {
int t=A/B;if(A%B==0)t--;
c=c-t*B;b=b-t*B;
return t+getfa(a,b,c);
}//依次模拟跳跃过程,实际上也是一个倍增的过程
}
迭代写法:没压行
void jump(int &a,int &b,int &c,int cha){//跳 cha 步
while(cha>0){
int A=b-a,B=c-b;
if(A<B){
int t=B/A;if(B%A==0)t--;//maxisum=t
if(t<=cha)cha-=t,a=a+t*A,b=b+t*A;
else{
a=a+cha*A,b=b+cha*A;cha=0;
}
}
else{
int t=A/B;if(A%B==0)t--;
if(t<=cha)cha-=t,c=c-t*B,b=b-t*B;
else{
c=c-cha*B,b=b-cha*B;cha=0;
}
}
}
return ;
}
然后就可以把三个坐标看成一个状态,进行正常的求 \(LCA\) 的过程。
但是这好像还有一个问题,怎么具体找到 \(LCA\) 的位置。
- 二分距离。
具体来说,二分两个结点到 \(LCA\) 的距离,然后往上跳 \(mid\) 次即可。
[省选联考 2021 A/B 卷] 宝石 就运用了二分处理树上路径的方法。我当时就没想二分
所以,二分对于处理不确定问题 有很大帮助。
- 倍增。
好想但是不好写(较二分而言)。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
template <typename T>
inline T read(){
T x=0;char ch=getchar();bool fl=false;
while(!isdigit(ch)){if(ch=='-')fl=true;ch=getchar();}
while(isdigit(ch)){
x=(x<<1)+(x<<3)+(ch^48);ch=getchar();
}
return fl?-x:x;
}
#define read() read<int>()
int a,b,c,x,y,z;
void init(){
a=read();b=read();c=read();
if(a>b)swap(a,b);if(a>c)swap(a,c);if(b>c)swap(b,c);
x=read();y=read();z=read();
if(x>y)swap(x,y);if(x>z)swap(x,z);if(y>z)swap(y,z);
return ;
}
int getfa(int &a,int &b,int &c){//三个点的状态
int A=b-a,B=c-b;
if(A==B)return 0;
//(x,a,b)->(x+a,a,b-a);
if(A<B){
int t=B/A;if(B%A==0)t--;//跳t次
a=a+t*A;b=b+t*A;
return t+getfa(a,b,c);
}
else {
int t=A/B;if(A%B==0)t--;
c=c-t*B;b=b-t*B;
return t+getfa(a,b,c);
}
}
bool check(){
return (a==x && b==y && c==z);
}
void jump(int &a,int &b,int &c,int cha){
while(cha>0){
int A=b-a,B=c-b;
if(A<B){
int t=B/A;if(B%A==0)t--;//maxisum=t
if(t<=cha)cha-=t,a=a+t*A,b=b+t*A;
else{
a=a+cha*A,b=b+cha*A;cha=0;
}
}
else{
int t=A/B;if(A%B==0)t--;
if(t<=cha)cha-=t,c=c-t*B,b=b-t*B;
else{
c=c-cha*B,b=b-cha*B;cha=0;
}
}
}
return ;
}
#define Debug cerr<<"@"<<endl
int a1,b1,c1,x1,y1,z1;
int main(){
init();
a1=a;b1=b;c1=c;
x1=x;y1=y;z1=z;
int cost1=getfa(a,b,c),cost2=getfa(x,y,z);
if(!check()){puts("NO");return 0;}
a=a1;b=b1;c=c1;
x=x1;y=y1;z=z1;
if(cost1<cost2)swap(a,x),swap(b,y),swap(c,z),swap(cost1,cost2);
//(a,b,c)往上跳
int cha=cost1-cost2;
jump(a,b,c,cha);
int L=0,R=min(cost1,cost2);
int ans=0;
while(L<=R){
int mid=(L+R)>>1;
a1=a;b1=b;c1=c;
x1=x;y1=y;z1=z;//当前状态
jump(a1,b1,c1,mid);jump(x1,y1,z1,mid);
if(a1==x1 && b1==y1 && c1==z1)R=mid-1,ans=mid;
else L=mid+1;
}
printf("YES\n%d\n",(ans<<1)+cha);
return 0;
}
后记
对于构造题,把握题目性质至关重要,更特殊地,结合所学知识解决问题。
结论(性质)不是想出来的,是玩出来的。