首页 > 其他分享 >「BZOJ2144」跳跳棋-题解

「BZOJ2144」跳跳棋-题解

时间:2023-05-02 18:44:05浏览次数:42  
标签:BZOJ2144 题解 tot times 跳棋 int d2 节点 d1

「BZOJ2144」跳跳棋

个人评价

挺好的一道题,难点在于想到树这个结构和建树

1 题面

跳跳棋是在一条数轴上进行的。棋子只能摆在整点上。每个点不能摆超过一个棋子。我们用跳跳棋来做一个简单的游戏:棋盘上有 3 颗棋子,分别在 a,b,c 这三个位置。我们要通过最少的跳动把他们的位置移动成 x,y,z 。(棋子是没有区别的)跳动的规则很简单,任意选两颗棋子,选择两颗中一颗以另一颗为轴跳动。跳动后两颗棋子距离不变。一次只允许跳过 1 颗棋子。

这一坨还好,简单说:

总共给出6个点,前三个为初始点,后三个为目标点,有一个操作,可以将一个点为中轴,然后另外任意两个点选一个来跳,问至少跳几次就可以从3个初始节点到目标节点

2 分析题面

第一眼看,这个题跟树有毛个关系啊

第一时间想到的暴力肯定是dfs乱跑一通,但是绝对会炸掉,因为好像没有什么优秀的限制条件

我们手动算一下,每次有(3 * 2)=6次操作,也就是说每个点会有6种变化,有一点点恐怖

但是我们看一下,有几次实际上是在将这个三个点的包括范围扩大,所以对于答案没有影响(显然吧)

那么我们是不是就可把没用的状态删掉,那么就可以得到小小的优化

我们把推的状态和初始点建成一颗树,那是不是就感觉来了?

接下来仔细来分析下下

2.1大致分析和小小尝试

根据给的三个点a,b,c,我们维护一个递增三元组\((a,b,c)\)a<b<c

每次跳跃分三种情况:

  1. b向c方向跳(点为\(2c-b\),三元组化为\((a,c,2c-b)\))

  2. b向a方向跳(点为\(2a-b\),三元组化为\((2a-c,a,c)\))

  3. 两边往中间跳:

    1.当\(b-a\)<\(c-b\)时\(a\)往\(b\)方向跳(点为\(2b-a\),三元组化为(\(b,2b-a,c\)))

    2.当\(b-a\)>\(c-b\)时\(c\)往\(b\)方向跳(点为\(2b-c\),三元组化为(\(a,2b-c,b\)))

那肯定有人要问不是还有其他情况吗,但是你手动算一下,你的其他情况相当于是吧范围进行扩大,对最后答案的寻找没有影响,反而增加了状态的转移,使得计算变得复杂,所以舍掉

我们接下来就可以得到一个四叉树!?


我们无法确定这颗树到底有多大好像无限大的说,这4个状态只有两边往中间跳有限制(题目要求不能两个点在同一个位置,即d1!=d2)

那这个题不就无解了还不如打表加暴力?接下来进行一些神奇操作

2.2优化和正解

2.2.1 优化建树

观察是不是有在4种情况中,有两组两个的三元组有一个共同的没变化的点

我们把有共同无变化的点的三元组放在一起,这样4个状态就变成了2个

具体操作如下:

设\(d1=b-a,d2=c-b\)

讨论\(d1>d2\)的情况(d1<d2 同理)也就是考虑\(c\)往\(b\)的方向跳

c要跳到\(a\)与\(b\)之间也就是说设变换后的点\(c\)为\(c1\)其中a< c1 <b

每次跳跃距离为\(d2\),跳跃总距离为\(d1-1\)那么次数为\((d1-1)/d2\)

设跳跃次数为t,那么多元组化为\((a,b-t*d2,c-t*d2)\)

每次有一个限制条件就是当d1=d2时就不行了

树大概长这个样子:

每次对于期望和起点,只要最后不是在同一颗树,也就是在dfs的结尾中,值(叶子节点)不一样,就无解

到这里我们就建完2颗二叉树了恭喜你到现在得到了10分

2.2.2使用lca

接下来就是这个题目的另一个难点——如何使用lca

我们观察两颗树,肯定有一个树是令一颗深度(即操作次数)的子树废话,都在同一颗树里面了

举个例子(例子中目标节点生成的树为子树,初始节点为父亲树)

我们要从初始节点到目标节点同一深度才可以找lca对吧

我们从初始节点进行t=A.times-B.times(A.times为初始节点到叶子节点的操作次数,B.times同理)

要是运气好,进行t次操作就得到目标节点了,ans=t

要是运气太拉了,到了目标节点的兄弟节点,我们就要lca了

但是我们怎么找这个lca呢?看之前的优化公式,我们是不是可以根据这个优化公式倒着上去找lca呢

那就可以二分答案这个上去的次数

2.2.3二分答案

二分答案从当前节点到lca节点需要的操作次数cnt,跟那个dfs长得差不多,具体操作见代码,

最后答案是A.times-B.times+2*cnt

3 代码实现(注释)

3.1定义

搞个结构体

struct G{
	int x,y,z,times;//x,y,z为递增三元组,times表示从操作前的根节点到叶子节点需要操作的次数
}A,B;

3.2 输入

先存入一个数组,然后排序,满足递增三元组要求

int a,b,c,x,y,z;
for(int i=1;i<=3;i++)scanf("%d",&t1[i]);
for(int i=1;i<=3;i++)scanf("%d",&t2[i]);
sort(t1+1,t1+4);
sort(t2+1,t2+4);
a=t1[1],b=t1[2],c=t1[3];
x=t2[1],y=t2[2],z=t2[3];

3.3 计算

1.先dfs跑出两个二叉树其实不是树,只是树对于想题来说好一点

2.判断是不是在同一颗二叉树里面

3.从父亲树的根节点跑到跟子树同深度

4.二分答案

3.4 输出

A.times-B.times+cnt*2

3.5 总体代码

带上注释

#include<bits/stdc++.h>
using namespace std;
int t1[5],t2[5];
struct G{
	int x,y,z,times;
}A,B;
int dfs(int a1,int b1,int c1,int k){//k是用来判断是初始节点建的树还是目标节点
	int cnt=0;
	while(1){
		int d1=b1-a1,d2=c1-b1;
		if(d1==d2)break;
		if(d1>d2){//分析的优化
			int t=(d1-1)/d2;//减一是为了防止两个点跳一起了
			cnt+=t,b1-=t*d2,c1-=t*d2;//推导的公式
		}else{
			int t=(d2-1)/d1;//同理
			cnt+=t,a1=a1+t*d1,b1=b1+t*d1;//d1<d2与上面同理
		}
	}
	if(k){//将两个树的叶子节点存着,方便判断
		A.x=a1,A.y=b1,A.z=c1,A.times=cnt;
	}else{
		B.x=a1,B.y=b1,B.z=c1,B.times=cnt;
	}
}
bool check(int t,int a,int b,int c,int x,int y,int z){//二分
	int tot=t;
    //这里的操作和dfs是差不多的
	while(tot){//开始找lca了
		int d1=b-a,d2=c-b;
		if(d1==d2)break;
		if(d1>d2){
			int t=min((d1-1)/d2,tot);//min和max都是为了防止炸掉
			tot=max(0,tot-t),b-=t*d2,c-=t*d2;
		}else{
			int t=min(tot,(d2-1)/d1);
			tot=max(tot-t,0),a=a+t*d1,b=b+t*d1;
		}
	}
	tot=t;
	while(tot){//跟上面同理
		int d1=y-x,d2=z-y;
		if(d1==d2)break;
		if(d1>d2){
			int t=min((d1-1)/d2,tot);
			tot=max(tot-t,0),y-=t*d2,z-=t*d2;
		}else{
			int t=min((d2-1)/d1,tot);
			tot=max(tot-t,0),x=x+t*d1,y=y+t*d1;
		}
	}
	if(a==x&&y==b&&z==c)return 1;//是不是满足条件
	else return 0;
}
int main(){
	int a,b,c,x,y,z;
	for(int i=1;i<=3;i++)scanf("%d",&t1[i]);
	for(int i=1;i<=3;i++)scanf("%d",&t2[i]);
	sort(t1+1,t1+4);//构造递增三元组
	sort(t2+1,t2+4);//排序
	a=t1[1],b=t1[2],c=t1[3];
	x=t2[1],y=t2[2],z=t2[3];
	dfs(a,b,c,1),dfs(x,y,z,0);
	if(A.x!=B.x||A.y!=B.y||A.z!=B.z){//判断是不是在同一颗树里面
		printf("NO");
		return 0;
	}else{
		printf("YES\n");
	}
	if(A.times<B.times){//让A里面放父亲树的值
		swap(a,x),swap(b,y),swap(c,z); 
		swap(A.times,B.times);
	}
	int tot=A.times-B.times;
	while(tot){//跑到同一深度
		int d1=b-a,d2=c-b;
		if(d1==d2)break;
		if(d1>d2){
			int t=min((d1-1)/d2,tot);
			tot-=t,b-=t*d2,c-=t*d2;
		}else{
			int t=min((d2-1)/d1,tot);
			tot-=t,a=a+t*d1,b=b+t*d1;
		}
	}
	int l=0,r=max(A.times,B.times),cnt1=0;
	while(l<=r){//开始二分
		int mid=(l+r)>>1;
		if(check(mid,a,b,c,x,y,z)){
			r=mid-1;
			cnt1=mid;
		}else{
			l=mid+1;
		}
	}
	printf("%d",cnt1*2+A.times-B.times);
}

4 总结

1.除了建树和优化难想到,别的都还好

2.跟倍增没有任何关系

3.更加会二分和构造了?

标签:BZOJ2144,题解,tot,times,跳棋,int,d2,节点,d1
From: https://www.cnblogs.com/Zhangrx-/p/17368036.html

相关文章

  • Educational Codeforces Round 147 (Rated for Div. 2) 题解
    ALink。模拟,代码。BLink。模拟,代码。CLink。我们设\(c\)为最后相同的字符。性质:我们一定不会删除字符\(c\)。因此以\(c\)为最后字符的操作次数就是不包含字符\(c\)的极大段的最小操作次数的最大值。对于一个长度为\(l(l\ge1)\)的段,它的最小操作次数为\(\l......
  • P4198 楼房重建 题解
    P4198楼房重建题解线段树二分思路考虑在线段树内维护二信息:区间斜率最大值\(mx\)区间最大斜率上升序列长度\(len\)答案即为根节点的\(len\)。考虑转移信息二:蓝色部分代表左区间的上升序列,红色是右区间的,绿色折线就是当前区间的上升序列。......
  • P2051 [AHOI2009] 中国象棋 题解
    DP。状态设计是点睛之笔。首先显然有每行或每列只能有至多\(2\)个棋子。设状态\(f_{i,j,k}\)为第\(i\)行,有\(j\)列只放了一个棋子,\(k\)列放了两个棋子。之后直接转移即可。注意边界判断。code:点击查看代码#include<bits/stdc++.h>#definelllonglong#defined......
  • Java cmd下编译乱码问题解决办法
    1、报错样式 2、解决办法1)指定字符集,如下 2)修改编码格式通过“记事本”打开—》另存为3)修改环境变量此电脑——》属性——》高级系统设置——》环境变量——》(系统环境变量)新建——》“JAVA_TOOL_OPTIONS” “-Dfile.encoding=UTF-8”如下图:——》重启cmd,再......
  • CF1034D Intervals of Intervals 题解
    传送门CF1034DIntervalsofIntervals题目大意有\(n\)个线段,第\(i\)个是\([a_i,b_i]\)。定义区间\([l,r]\)的价值是第\(l\)个线段到第\(r\)个线段的并的长度。找出\(k\)个不同的区间,使得总价值最大。输出最大总价值。\(1\len\le3\times10^5,1\lek\le......
  • asm_second 题解(坐标转换+二维偏序)
    QuestionAsm.Def在第一象限内找到了n个可疑点。他需要为导弹规划路径。如图所示,导弹一开始在(0,0)。它只能朝着一定的方向——即严格夹在图中两条射线间的方向(白色部分)前进。注意,它不能沿着这两条射线前进,当然也不能停在原地。当导弹到达某个可疑点后,它仍然只能朝着该范围内......
  • 2023湖北CCPC省赛 蒻蒟的部分题解
    题目地址C.DarknessI题意:有一个n*n的方格,最开始全是白色,如果白色周围4格有两个黑色格子,1秒后这个白色格子会变成黑色,问如果要使全部格子都变为黑色,最开始最少需要涂黑几个格子Solution对于两个黑色格子,只有当满足\[|x_1-x_2|+|y_1-y_2|≤2\]才能涂黑以这两个格子为顶点的矩......
  • 2023 Hubei Provincial Collegiate Programming Contest题解 C F H I J K M
    补题链接:https://codeforces.com/gym/104337原文链接:https://www.eriktse.com/algorithm/1136.htmlM.DifferentBilling签到题,写几个柿子然后枚举B或C即可。#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;signedmain(){ ios::sync_with_stdio(......
  • CF1477F Nezzar and Chocolate Bars 题解
    题意:有一根长为\(1\)的巧克力,已经被切了\(m-1\)刀被分成\(m\)分,接下来每次在整根长度为\(1\)的巧克力上均匀随机一个点切一刀,求每一小段巧克力长度均小于一个给定值\(K\)需要的期望次数。引理:Irwin-Hall分布:对于\(n\)个在\([0,1]\)内均匀分布的实数随机变量,它们......
  • 4 月 30 日测试题解
    4月30日测试题解T1\({\color{green}{\text{100pts}}}\text{/100pts}\)题意一个无限长宽的棋盘,给出起点\(s\)和终点\(t\),行走方式是象棋中马的走法,问最少需要走多少步。对于\(100\%\)的数据,\(|x_s|,|y_s|,|x_t|,|y_t|\le10^7\)。思路\(\text{100pts}\)首先,坐......