题解 P5025【[SNOI2017]炸弹】
Krystallos · · 题解
提供一种短小精悍的
P.S. 下文中
首先有一个结论:无论如何引爆,如果炸弹
先把所有
- 判断自己能否引爆炸弹
l_i - 1 ,如果不能或者l_i = 1 ,终止操作 - 比较自己的爆炸右边界
(x_i + range_i) 与l_i - 1 的爆炸右边界(x_{l_i - 1} + range_{l_i - 1}) ,如果后者更大那么用后者减去x_i 的值来更新range_i - 将
l_i 更新为l_{l_i - 1} ,并返回第一个操作
这样一轮算下来之后我们就可以计算出只往左爆炸能够到达的边界
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];
}
}
简单证一下复杂度,外层循环显然是线性的;内层循环中,会存在某一个炸弹
类似的,我们可以从右往左扫一遍来计算答案右端点
- 判断自己能否引爆炸弹
r_i + 1 ,如果不能或者r_i = n ,终止操作 - 比较自己的爆炸左端点
l_i 与先往右炸再往左炸的答案l_{r_i + 1} ,如果后者更小则用后者更新前者 - 将
r_i 更新为r_{r_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];
}
}
这里的
最后引爆炸弹
完整代码:
#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;
}
效率没的说,最优解榜一就是这个代码
或者说其实这玩意的正确性和效率是假的?(欢迎来 Hack