题目描述
在一条直线上有 n 个炸弹,每个炸弹的坐标是 xi,爆炸半径是 ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 xj 满足: ∣xj−xi∣≤ri ,那么,该炸弹也会被引爆。
现在,请你帮忙计算一下,先把第 i 个炸弹引爆,将引爆多少个炸弹呢?
答案对 109+7 取模。
输入格式
第一行,一个数字 n ,表示炸弹个数。 第 2∼n+1 行,每行两个整数,表示 xi,ri,保证 xi 严格递增。
输出格式
一个数字,表示 i=1∑ni× 炸弹 i 能引爆的炸弹个数。
输入输出样例
输入 #1
4
1 1
5 1
6 5
15 15
输出 #1
32
说明/提示
【数据范围】
对于 20% 的数据: n≤100。
对于 50% 的数据: n≤1000。
对于 80% 的数据: n≤100000。
对于 100% 的数据: 1≤n≤500000,−1018≤xi≤1018,0≤ri≤2×1018。
题解
cpp
#include <cstdio>
#include <iostream>
const int nn = 5e5 + 5, mod = 1e9 + 7;
int n, l[nn], r[nn];
long long ans, a[nn], range[nn];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%lld %lld", a + i, range + i);
l[i] = r[i] = i;
}
for (int i = 2; i <= n; i++) {
while (l[i] > 1 && a[i] - a[l[i] - 1] <= range[i]) {
range[i] = std::max(range[i], range[l[i] - 1] - (a[i] - a[l[i] - 1]));
l[i] = l[l[i] - 1];
}
}
for (int i = n - 1; i >= 1; i--) {
while (r[i] < n && a[r[i] + 1] - a[i] <= range[i]) {
l[i] = std::min(l[i], l[r[i] + 1]);
r[i] = r[r[i] + 1];
}
}
for (int i = 1; i <= n; i++)
ans = (ans + 1ll * i * (r[i] - l[i] + 1)) % mod;
printf("%lld\n", ans);
return 0;
}
无论如何引爆,如果炸弹 l 和 r 被引爆了 (l≤r),那么任何炸弹 p∈l,r 都会被引爆。那么考虑维护最开始引爆炸弹 i 后,所有最后被引爆的炸弹区间 li,ri。
先把所有 li,ri 初始化为 i,i,然后令 i 从 2 开始,到 n 结束,执行以下操作:
- 判断自己能否引爆炸弹 li−1,如果不能或者 li=1,终止操作
- 比较自己的爆炸右边界 (xi+rangei) 与 li−1 的爆炸右边界 (xli−1+rangeli−1),如果后者更大那么用后者减去 xi 的值来更新 rangei
- 将 li 更新为 lli−1,并返回第一个操作
这样一轮算下来之后我们就可以计算出只往左爆炸能够到达的边界 li,时间复杂度是线性的
(创作不易,感谢点赞,原创切勿抄袭,抄袭可耻)