问题描述
一条无限长的直线上分布着一些机器人和墙壁。给定整数数组 robots、distance 和 walls:
robots[i]是第 i 个机器人的位置。distance[i]是第 i 个机器人的子弹最大射程。walls[j]是第 j 堵墙的位置。
每个机器人有一颗子弹,可以向左或向右发射,最远距离为 distance[i]。子弹会摧毁其射程内路径上的每一堵墙。但机器人是固定的障碍物:如果子弹在到达墙壁前击中另一个机器人,它会立即在该机器人处停止,无法继续前进。
返回机器人可以摧毁墙壁的最大数量。注意:墙壁和机器人可能在同一位置;该位置的墙壁可以被该位置的机器人摧毁。机器人不会被子弹摧毁。
示例:
输入:robots = [4, 10], distance = [3, 5], walls = [1, 5, 7, 10]
输出:3
解释:机器人 0 向左射击覆盖 [1, 4],摧毁墙 1;机器人 1 向左射击覆盖 [5, 10],摧毁墙 5 和 7(墙 10 被自身挡住或视为已处理)。具体需根据遮挡逻辑计算。
核心思路分析
这道题的难点在于子弹会被其他机器人阻挡,导致射程受限。直接模拟所有情况复杂度太高,我们需要利用排序和动态规划来优化。
关键性质
- 遮挡效应:子弹遇到最近的机器人就会停止。这意味着对于任意一堵墙,它只能被相邻的机器人摧毁。如果两个机器人之间没有墙,它们互不影响;如果有墙,则取决于谁先'够得着'。
- 相对顺序:由于机器人位置固定,我们可以按坐标从小到大排序处理。这样在处理当前机器人时,只需要考虑它与前一个机器人的关系。
- 坐标范围大:坐标可达 $10^9$,不能直接用数组存储状态,必须使用离散化或哈希映射。
动态规划状态设计
我们定义 dp[i][0] 表示处理到第 i 个机器人,且第 i 个机器人向左射击时的最大摧毁墙数。
定义 dp[i][1] 表示处理到第 i 个机器人,且第 i 个机器人向右射击时的最大摧毁墙数。
为了简化边界处理,可以在机器人列表首尾各添加一个虚拟机器人,位置分别在负无穷和正无穷,射程为 0。
状态转移
假设当前机器人为 i,前一个机器人为 i-1。
- 向左射击:
- 如果
i-1也向左,两者互不干扰,直接累加。 - 如果
i-1向右,两者可能产生重叠区域。需要计算重叠部分是否重复统计了墙的数量。若重叠,需减去重复部分。
- 如果
- 向右射击:
- 通常不会与前一个机器人的向左射击冲突(因为方向相反),主要受限于后一个机器人的位置。
内存优化与离散化
直接使用线段树维护区间最大值会导致内存超限(坐标范围太大)。解决方案是对所有关键坐标(机器人位置、墙壁位置、机器人射程边界)进行离散化。
离散化后,坐标范围缩小到 $O(N)$ 级别,此时可以使用线段树或树状数组来维护 dp[x] - g(x) 的最大值,其中 g(x) 是前缀墙数。这样可以将查询和更新操作优化到 $O( ext{log} N)$。
代码实现
以下是经过优化的 C++ 实现,包含离散化处理及动态规划逻辑。
std;
{
:
{
n = robots.();
(walls.(), walls.());
vector<pair<, >> (n);
( i = ; i < n; ++i) {
rd[i] = {robots[i], distance[i]};
}
(rd.(), rd.());
vector<> coords;
( & p : rd) {
coords.(p.first);
coords.(p.first - p.second);
coords.(p.first + p.second);
}
( w : walls) {
coords.(w);
}
(coords.(), coords.());
coords.((coords.(), coords.()), coords.());
getIdx = [&]( val) {
(coords.(), coords.(), val) - coords.();
};
m = coords.();
vector<vector<>> (n, <>(, ));
countWalls = [&]( l, r) {
(l > r) ;
(walls.(), walls.(), r) - (walls.(), walls.(), l);
};
( i = ; i < n; ++i) {
pos = rd[i].first;
dis = rd[i].second;
leftBound = (i == ) ? INT_MIN : rd[i].first;
rightBound = (i == n - ) ? INT_MAX : rd[i].first;
lRange = (leftBound, pos - dis);
rRange = (rightBound, pos + dis);
cntLeft = (lRange, pos);
cntRight = (pos, rRange);
prevLeft = (i > ) ? dp[i][] : ;
prevRight = (i > ) ? dp[i][] : ;
overlap = ;
(i > && rd[i].first < pos) {
prevREnd = rd[i].first + rd[i].second;
currLStart = pos - dis;
overlapEnd = (prevREnd, pos);
overlapStart = (rd[i].first, currLStart);
(overlapStart <= overlapEnd) {
overlap = (overlapStart, overlapEnd);
}
}
dp[i][] = (prevLeft, prevRight) + cntLeft - overlap;
dp[i][] = (prevLeft, prevRight) + cntRight;
}
(dp[n][], dp[n][]);
}
};


