记录123
cpp
#include<bits/stdc++.h>
using namespace std;
const long long LIMIT=1e12;// 题目给出的上限,用来判断节点编号是否过大
int main(){//纯模拟
ios::sync_with_stdio(false);
cin.tie(0);
int n;// 定义整型变量n,用来存储总的移动次数
long long s;// 定义长整型变量s,存储初始节点编号(防溢出)
string ops;// 定义字符串ops,用来存储长度为n的移动指令序列
cin>>n>>s>>ops;
int level=0;// 记录超出LIMIT范围的相对层数(可以理解为"虚层"),初始为0表示在安全范围内
for(int i=0;i<n;i++){// 遍历整个指令字符串,对每一条指令进行实时决策
char op=ops[i];// 取出当前第i步的移动指令字符
if(op=='U'){// 如果当前指令是向上移动到父节点
if(s==1) continue;// 如果已经在根节点(编号为1),无法继续向上,什么都不做
if(level>0){// 如果之前在"虚层"(编号过大被隐藏了),向上走一步相当于减少一层虚层
level--;
}else{// 如果在真实编号的安全范围内,正常除以2找父节点
s/=2;
}
}
else if(op=='L'){// 如果当前指令是向左儿子移动 (编号 * 2)
// 如果已经在虚层,或者下一步计算会超出限制,就只增加虚层层级,不实际计算编号
if(level>0||s*2>LIMIT){
level++;
}else s*=2;// 在安全范围内,正常计算编号
}
else if(op=='R'){// 如果当前指令是向右儿子移动 (编号 * 2 + 1)
// 同理,如果超出限制则只增加虚层层级
if(level>0||s*2+1>LIMIT){
level++;
}else s=s*2+1;// 在安全范围内,正常计算编号
}
}
cout<<s<<endl;// 循环结束后,level一定被减回0,此时的s就是最终答案,直接输出
return 0;// 结束程序
}
前言
我是一名专注信奥赛(CSP-J/S、NOIP)的教练。
- 如果你觉得这篇题解对你有帮助,欢迎点击关注我的CSDN账号,我会持续更新高质量算法解析。
- 我深知算法思维的构建远比单纯通过题目更重要,本系列题解不局限于AC代码的堆砌,而是致力于拆解题目背后的逻辑链条与核心知识点
- 备赛路上若遇瓶颈,欢迎随时评论或私信,我将甄选典型疑难问题,通过视频讲解或撰写专项文章的形式,为你提供深度答疑。
题目传送门
https://www.luogu.com.cn/problem/P11375
💡 核心解题思路
这道题是经典的二叉树模拟与防溢出处理 问题。题目要求模拟在二叉树上的移动,但存在一个巨大的陷阱:虽然最终结果不超过 10121012 ,但在移动过程中,如果连续向左或向右走,节点编号会呈指数级增长,轻易突破 long long 的上限(约 9×10^18 ),导致溢出。
-
常规模拟:
- 向上移动(
U):当前节点编号除以 2(s /= 2),根节点 1 除外。 - 向左移动(
L):当前节点编号乘 2(s *= 2)。 - 向右移动(
R):当前节点编号乘 2 加 1(s = s * 2 + 1)。
- 向上移动(
-
破局关键:引入"虚层"(level)机制 :
为了防止在向下走时发生整数溢出,我们不能盲目计算。
- 当向下走(
L或R)且计算结果超过上限 10121012 时,我们不进行实际的乘法运算 ,而是用一个变量level记录"出界"的层数(即level++)。 - 当向上走(
U)时,如果level > 0,说明我们还在界外,此时只需要level--即可,无需做除法。 - 只有当
level == 0且当前节点在安全范围内时,才进行真实的s /= 2运算。 - 题目保证最终结果在 10^12 以内,这意味着循环结束时,
level一定会被减回 0,此时的s就是最终答案。
- 当向下走(
🧩 代码分块详细解释
1. 头文件与常量定义
cpp
#include<bits/stdc++.h>
using namespace std;
const long long LIMIT=1e12; // 定义安全边界常量,用于判断节点编号是否越界
- 作用:引入标准库并定义题目给出的上限阈值 10121012 ,作为后续判断是否进入"虚层"的标准。
2. 输入与初始化
cpp
int main(){
ios::sync_with_stdio(false);
cin.tie(0); // 关闭输入输出同步,提升大数据量下的读取速度
int n; // 记录总的移动次数
long long s; // 记录当前真实的节点编号(必须用 long long 防溢出)
string ops; // 存储长度为 n 的移动指令序列
cin>>n>>s>>ops; // 读入移动次数、初始节点和指令字符串
int level=0; // 核心变量:记录超出 LIMIT 范围的相对层数(虚层),0 表示在安全范围内
- 作用 :完成基础数据的读取,并初始化"虚层"计数器
level。
3. 核心模拟循环:向上移动 (U)
cpp
for(int i=0;i<n;i++){ // 遍历整个指令字符串,对每一条指令进行实时决策
char op=ops[i]; // 取出当前第 i 步的移动指令字符
if(op=='U'){ // 如果当前指令是向上移动到父节点
if(s==1) continue; // 特判:如果已经在根节点(编号为1),无法继续向上,直接跳过
if(level>0){ // 如果当前处于"虚层"(编号过大被隐藏了),向上走一步相当于减少一层虚层
level--;
}else{ // 如果在真实编号的安全范围内,正常执行除以 2 找父节点
s/=2;
}
}
- 作用:处理向上移动逻辑。优先消耗虚层深度,虚层耗尽后才执行真实的除法操作。
4. 核心模拟循环:向左/右移动 (L / R)
cpp
else if(op=='L'){ // 如果当前指令是向左儿子移动 (理论编号 * 2)
// 如果已经在虚层,或者下一步计算会超出限制,就只增加虚层层级,不实际计算编号
if(level>0||s*2>LIMIT){
level++;
}else s*=2; // 在安全范围内,正常计算编号
}
else if(op=='R'){ // 如果当前指令是向右儿子移动 (理论编号 * 2 + 1)
// 同理,如果超出限制则只增加虚层层级
if(level>0||s*2+1>LIMIT){
level++;
}else s=s*2+1; // 在安全范围内,正常计算编号
}
}
- 作用 :处理向下移动逻辑。通过预判
s*2或s*2+1是否越界,决定是真实计算还是仅仅记录虚层。
5. 输出与结束
cpp
cout<<s<<endl; // 循环结束后,level 一定被减回 0,此时的 s 就是最终答案,直接输出
return 0; // 结束程序
}
- 作用 :输出最终停留在的安全节点编号。由于题目保证最终结果不越界,循环结束时
level必然归零。
📊 核心逻辑总结表
| 代码模块 | 核心变量/操作 | 精炼作用 | 解决的痛点 |
|---|---|---|---|
| 边界定义 | LIMIT = 1e12 |
设定安全阈值 | 为判断是否溢出提供量化标准 |
| 状态记录 | level |
记录虚层深度 | 避免在树极深处进行大数运算导致溢出 |
| 向上移动 | level-- / s/=2 |
优先回退虚层 | 确保从极深节点返回时不会发生除零或错误计算 |
| 向下移动 | level++ / s*=2 |
预判越界并标记 | 拦截即将溢出的乘法操作,将其转化为安全的层级记录 |
| 根节点特判 | if(s==1) continue |
防止无效操作 | 避免在根节点执行 U 操作时产生逻辑错误 |