【题解】P7599 | 倍增 贪心 分类讨论
0.闲话
做不会的题,要么自己想,要么问一个人,不要同时问几个做法不同的人 / 看几篇做法不同的题解,这些可以等你 AC 后再看,效率会高很多。
总之还是感谢 L7_56 大佬讲解 %% ,虽然没听懂,但是自己磨出来了。
很多题解是 APIO 刚完的时候写的,故讲解不太仔细,在这里补充一下。
1.题目相关
简化题意:
一排树,高度互不相同,每次跳跃会跳到左边第一个比它高的点,或右边第一个比它高的点,多次询问从 中任意点到 中任意点的最少步数,强制在线。
数据范围:
原题:
2.约定与记号
本文对树的编号从1开始,在开始和结尾放两棵无限高的树不影响答案。
表示 的高度 , 表示 左边第一个比它高的点 , 表示 右边第一个比它高的点 。
约定 本文中表示 , 本文中表示
特别地,
3.算法,思维过程
首先有 ,与正解没啥关系,故不讨论。
1.
观察特殊数据,考虑对于 的特殊数据,思考如何处理。
首先,有解的充要条件是 , 若不满足,一定会经过 高度为 的这个点 且无法到达 , 否则一直向右跳则有解。
若有目前在 点 , , 则跳到 中高度更高的点不会更劣。
证明是显然的 , 较低者能跳到的两个边界其中之一是较高者,另一边是 , 较高者的两个边界之一为 ,另一边界显然不会更小。
所以一定是会从起点开始,不断向 中高度更高的点跳跃,直到两者中较高者高度大于等于终点高度。
接下来的最优决策就是一直向右跳,
若 大于等于终点高度,又因为有解的条件,此时 显然是终点
若 大于等于终点高度(此时显然是大于) , 那么就显然一直向右跳,一定也是满足在不超过终点的高度跳尽可能高。
使用两次倍增 (一次跳尽可能高的点 , 一次跳尽可能右的点 ) 即可解决 。
即 表示从 向右开始跳 步 , 表示从 向尽可能高跳 步 , 然后常规倍增。
2.
先只考虑有解的情况,即
接下来考虑起点的选择 , 令 中最大值的位置为 ,若 则可以从 一步走过去 , 否咋选择一个满足 中的条件的一个最高的点 作为起点,由于前部分是尽可能向上走,同 中的证明,选择高的很明显不劣。
这个点就是 中的最大值 , 因为有解且 中的点都是符合条件的。
3.一般情况
一个特殊的值是 中的最大值所在点p , 分两种情况进行考虑 , 经过它,不经过。
3.1 经过它
选择 中的最大值走过去,同 2 即可 , 走到 后一步即可走到。
3.2 不经过它
不经过 那么就一定经过 左边的某个比它大的点 , 这种情况必须满足 , 显然 。
反证:若 , 因为不经过 p , 显然是从 左边的某个比 大的点走过去 , 此时 是不能到达 的 , 左边的低于 的要么经过 , 要么只会走到高于 的点 , 所以一定满足条件。
若不满足条件,则必须经过
若 则可以一步走到
否则选取 中的最大值向 跳即可,然后一步跳到终点区间。
常规倍增(同 1 )即可,见配图。
配图:
无解
1 这种经过 的路径不行。
2 这种高于 的点的 不会更靠左。
两种路径
1 经过
2 不经过
使用倍增进行“跳”, ST 表进行 RMQ 查询 , 时空复杂度 。
4.代码实现
关键部分
/*分情况,特判*/
int minimum_jumps(int A, int B, int C, int D) {
A++,B++,C++,D++;
if(mx(B,C-1).first>mx(C,D).first) return -1;
/*判掉无解*/
auto mx1 = mx(B,C-1);
if(mx1.second == B) return 1 ;
int p = mx1.second , Lp = l[p] ,res = 1e9 + 100 ;
/*过p*/
int st = mx(max(A,Lp + 1) , B).second ;
res = min(res , calc1(st , p) + 1);
/*到 R[l[p]]*/
if(r[Lp]>=C&&r[Lp]<=D) {
if(Lp>=A) res = min(res , 1);
else res = min(res , calc2(mx(A,B).second,Lp) + 1) ;
}
if(res >= 50000000) return -1;
return res;
}
/*两个倍增*/
int calc1(int st,int ed) {
int ret = 0 ;
if(mx(st,ed-1).first > h[ed]) return 110000000 ;
for(int i = Lg - 1 ; i >= 0 ; -- i) {
if(h[Up[i][st]]>h[ed]) continue;
st = Up[i][st] , ret += (1 << i) ;
}
debug(st) ;
for(int i = Lg - 1 ; i >= 0; -- i) {
if(Rt[i][st] <= ed && Rt[i][st]) st = Rt[i][st] , ret += (1<<i);
}
if(st == ed)return ret;
return 110000000;
}
int calc2 (int st,int ed) {
int ret = 0 ;
if(mx(ed+1,st).first > h[ed]) return 110000000 ;
for(int i = Lg - 1 ; i >= 0 ; -- i) {
if(h[Up[i][st]]>h[ed]) continue;
st = Up[i][st] , ret += (1 << i) ;
}
for(int i = Lg - 1 ; i >= 0 ; -- i) {
if(Lf[i][st] >= ed && Lf[i][st]) st = Lf[i][st] , ret += (1<<i) ;
}
if(st == ed)return ret;
return 110000000;
}
完整代码
本文已经结束了。本文作者:ღꦿ࿐(DeepSea),转载请注明原文链接:https://www.cnblogs.com/Dreamerkk/p/17970991,谢谢你的阅读或转载!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步