1. 问题分析与解题思路
这道题目描述了一个有趣的场景:国王命令国民在一条无限延伸的道路上散步,当两个人相遇时会停下来交谈。我们需要在给定时间T后,快速查询Q个重要人物的位置。
1.1 问题核心理解
问题的核心在于处理大量人物(最多10^5个)在长时间(最多10^18秒)内的运动情况。直接模拟每秒的运动显然不可行,因为时间复杂度会爆炸(O(TN))。
关键观察点:
- 人物初始位置按升序排列
- 每个人只有两种运动方向:向东(坐标增加)或向西(坐标减少)
- 相遇条件:两个人位置相同(包括运动中的人和已停止的人)
1.2 解题思路突破
我们可以利用以下性质来优化算法:
- 只有相邻的、运动方向相反的人才会相遇
- 一旦两个人相遇,他们就会停止在相遇点
- 相遇点一定是两个初始位置的中点(因为速度相同)
基于此,我们可以:
- 预处理每个人可能遇到的"阻挡者"
- 计算每个人在时间T内能走的最大距离
- 取实际行走距离和阻挡距离中的较小值
2. 算法设计与实现细节
2.1 数据结构选择
我们使用以下数据结构:
p[i][0]:第i个人的初始位置p[i][1]:第i个人的运动方向(1向东,2向西)loc[i]:第i个人的最终位置
2.2 关键算法步骤
算法分为两个主要阶段:
2.2.1 处理向西行走的人(方向为2)
从左向右遍历:
cpp复制for(int i=1;i<=n;i++) {
if(p[i][1]==2) {
if(i==1) {
// 最左边的人,无阻挡
loc[i] = p[i][0]-t;
} else if(p[i-1][1]==2) {
// 前一个人也向西,取两者中更东的位置
loc[i] = max(loc[i-1], p[i][0]-t);
} else {
// 可能被前一个人阻挡
loc[i] = max((p[i][0]+p[i-1][0])/2, p[i][0]-t);
}
}
}
2.2.2 处理向东行走的人(方向为1)
从右向左遍历:
cpp复制for(int i=n;i>=1;i--) {
if(p[i][1]==1) {
if(i==n) {
// 最右边的人,无阻挡
loc[i] = p[i][0]+t;
} else if(p[i+1][1]==1) {
// 后一个人也向东,取两者中更西的位置
loc[i] = min(loc[i+1], p[i][0]+t);
} else {
// 可能被后一个人阻挡
loc[i] = min((p[i][0]+p[i+1][0])/2, p[i][0]+t);
}
}
}
2.3 时间复杂度分析
- 预处理阶段:两次遍历O(N)
- 查询阶段:每次查询O(1),总共O(Q)
- 总时间复杂度:O(N+Q),完美处理大规模数据
3. 代码实现与优化技巧
3.1 完整代码解析
cpp复制#include<iostream>
#define maxn 100010
using namespace std;
int n,Q,q[maxn];
long long loc[maxn],p[maxn][2],t;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
// 输入处理
cin>>n>>t>>Q;
for(int i=1;i<=n;i++)
cin>>p[i][0]>>p[i][1];
for(int i=1;i<=Q;i++)
cin>>q[i];
// 处理向西走的人
for(int i=1;i<=n;i++) {
if(p[i][1]==2) {
if(i==1)
loc[i]=p[i][0]-t;
else if(p[i-1][1]==2)
loc[i]=max(loc[i-1],p[i][0]-t);
else
loc[i]=max((p[i][0]+p[i-1][0])/2,p[i][0]-t);
}
}
// 处理向东走的人
for(int i=n;i>=1;i--) {
if(p[i][1]==1) {
if(i==n)
loc[i]=p[i][0]+t;
else if(p[i+1][1]==1)
loc[i]=min(loc[i+1],p[i][0]+t);
else
loc[i]=min((p[i][0]+p[i+1][0])/2,p[i][0]+t);
}
}
// 输出查询结果
for(int i=1;i<=Q;i++)
cout<<loc[q[i]]<<endl;
return 0;
}
3.2 优化技巧
-
输入输出优化:
- 使用
ios::sync_with_stdio(false)和cin.tie(0)加速IO - 这在处理大规模数据时非常关键
- 使用
-
空间优化:
- 只存储必要信息,避免不必要的数组
- 使用
long long类型处理大数
-
逻辑简化:
- 分开处理不同方向的人
- 利用数学计算而非模拟
4. 常见问题与调试技巧
4.1 常见错误
-
整数溢出:
- 位置坐标和T都可能达到1e18
- 必须使用
long long而非int
-
边界条件:
- 处理第一个和最后一个人时没有前驱/后继
- 相遇点计算时的整数除法
-
方向判断错误:
- 确保1表示向东,2表示向西
- 方向判断条件写反是常见错误
4.2 调试技巧
-
小规模测试:
- 先用手算小案例验证算法正确性
- 例如题目给出的样例
-
极端情况测试:
- 所有人同方向
- T=0的情况
- 最大N和最大T的情况
-
中间输出:
- 打印每个人的中间计算结果
- 检查相遇点是否正确
5. 算法扩展与变种思考
5.1 类似问题
-
多人相遇问题:
- 如果相遇后不是停止而是改变方向
- 需要更复杂的模拟
-
不同速度:
- 每个人速度不同
- 需要计算更精确的相遇时间
-
环形道路:
- 道路首尾相连
- 需要考虑绕圈相遇的情况
5.2 性能优化
对于更大的数据规模:
- 并行处理:
- 东西方向的预处理可以并行
- 分段处理:
- 将人物分成若干段独立处理
- 更高效的数据结构:
- 使用树状数组或线段树处理区间查询
6. 实际应用与教学价值
这道题目虽然设定有趣,但实际考察的是:
- 问题抽象能力:将生活场景转化为算法问题
- 优化思维:避免暴力模拟,寻找数学规律
- 编码实现:正确处理边界条件和特殊情形
在教学上,这道题适合用来训练:
- 双指针技巧
- 预处理思想
- 大规模数据处理意识
我在实际解题中发现,这类问题在竞赛中很常见,核心是要找到问题中的不变量和规律,避免被表面现象迷惑。这道题的巧妙之处在于利用人物初始有序和运动方向的特点,将O(TN)的问题转化为O(N)的预处理问题。