[题解] 跳跳棋

[题解] 跳跳棋

构造题,好题!

传送门

题意

数轴上有三个点,他们之间不可重叠。

一个点只能沿另一个点对称的方向和长度跳跃,且一次能且仅能越过一个点。

每一次上述操作看做一个步骤,给出当前状态和结果状态(\(6\) 个坐标),输出解数。

解题报告

手玩一下这个过程,可以把状态的变换分为两类:

  1. 两边向中间跳。(比如上图)

  2. 中间向两边跳。

由于:

一次 能且仅能 越过一个点。

所以对于操作 \(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\) 的位置。

  1. 二分距离。

具体来说,二分两个结点到 \(LCA\) 的距离,然后往上跳 \(mid\) 次即可。

[省选联考 2021 A/B 卷] 宝石 就运用了二分处理树上路径的方法。我当时就没想二分

所以,二分对于处理不确定问题 有很大帮助。

  1. 倍增。

好想但是不好写(较二分而言)。

#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;
}

后记

对于构造题,把握题目性质至关重要,更特殊地,结合所学知识解决问题。

结论(性质)不是想出来的,是玩出来的。

posted @ 2021-08-12 17:29  ¶凉笙  阅读(76)  评论(0编辑  收藏  举报