題意
link.
題解
一個好想一點的正解。
考慮到可能連鎖爆炸,我們不能透過一個單純的二分來解決問題。
考慮 dp。
記 \(f(i)\) 為第 \(i\) 個點爆炸,最遠能引爆到哪個座標小於它的點。
\(g(i)\) 為第 \(i\) 個點爆炸,最遠能引爆到哪個座標大於它的點。
我們以 \(f\) 為例,\(g\) 可以透過同樣的方法求得。
對於每一個 \(i\),二分出一個最小的 \(l\) 滿足 \(\lvert x_l - x_i \rvert \le r_i\),此時區間 \(\left [ l, i - 1 \right ]\) 就是 \(i\) 透過一次爆炸能爆到的。
但是可以連鎖爆炸,所以我們需要選出一個 \(t \in \left [ l, i - 1 \right ]\) 使得 \(f(t)\) 最小。
此時就可以轉移了:
但找出 \(t\) 的複雜度是 \(\mathcal O(n)\) 的,總時間複雜度是 \(\mathcal O (n^2 + n \log n)\) 的,這樣就可以光榮的獲得 \(55\) 分了。
考慮開一棵線段樹,維護 \(x_i - r_i\) 的最小值和最小值的編號。
因為滿足 \(f(t)\) 最小的 \(t\) 也一定滿足 \(x_i - r_i\) 最小,反之同理。
此時只需要查詢一下區間 \(\left [ l, i - 1 \right ]\) 最小值編號即可作為 \(t\) 轉移了。
\(g\) 只需要開一棵最大值線段樹即可。
然後你就會發現它錯了(貌似樣例都過不了)。
我們不能把思維固定在 \(f\) 轉移 \(f\),\(g\) 轉移 \(g\) 上。
有可能我們先爆炸炸到 \(i\) 右邊,然後透過引爆 \(i\) 右邊一枚非常強悍的炸彈然後再引爆到左邊。
所以要反著再轉移一次。
然後你就會發現它又錯了(貌似會錯第二個點)。
因為我們透過走右邊到達了左邊,此時我們還需要再走一次左邊,因為走過來不一定是最小的(真煩)。
右邊同理。
所以重複一邊上文的過程即可。
但值得慶幸的是,這麼就能過了,因為此時已經把所有方案考慮完了。
時間複雜度:\(\mathcal O (n \log n)\),帶一點線段樹的並不小的常數。
namespace zqh {
const int N = 5e5 + 5;
int n;
struct bomb { // 炸彈結構體
int x, r;
} a[N];
int dpl[N], dpr[N];
struct segment { // 線段
int mx, mx_id; // 最大值,最大值編號,下同
int mn, mn_id;
};
struct segment_tree { // 線段樹
#define ls (id << 1)
#define rs (id << 1 | 1)
segment seg[N << 2];
void pushup(int id) {
if (seg[ls].mn < seg[rs].mn) {
seg[id].mn = seg[ls].mn;
seg[id].mn_id = seg[ls].mn_id;
} else {
seg[id].mn = seg[rs].mn;
seg[id].mn_id = seg[rs].mn_id;
}
if (seg[ls].mx > seg[rs].mx) {
seg[id].mx = seg[ls].mx;
seg[id].mx_id = seg[ls].mx_id;
} else {
seg[id].mx = seg[rs].mx;
seg[id].mx_id = seg[rs].mx_id;
}
}
void build(int id, int lft, int rht) { // 建樹
seg[id].mn = LLONG_MAX;
seg[id].mx = LLONG_MIN;
if (lft == rht) {
seg[id].mn_id = seg[id].mx_id = lft;
seg[id].mn = a[lft].x - a[lft].r;
seg[id].mx = a[lft].x + a[lft].r;
return;
}
int mid = (lft + rht) / 2;
build(ls, lft, mid);
build(rs, mid + 1, rht);
pushup(id);
}
segment query(int id, int lft, int rht, int l, int r) {
if (rht < l || r < lft)
return {LLONG_MIN, -1, LLONG_MAX, -1};
if (l <= lft && rht <= r)
return seg[id];
int mid = (lft + rht) / 2;
segment t1 = query(ls, lft, mid, l, r),
t2 = query(rs, mid + 1, rht, l, r), ret;
if (t1.mn < t2.mn) {
ret.mn = t1.mn;
ret.mn_id = t1.mn_id;
} else {
ret.mn = t2.mn;
ret.mn_id = t2.mn_id;
}
if (t1.mx > t2.mx) {
ret.mx = t1.mx;
ret.mx_id = t1.mx_id;
} else {
ret.mx = t2.mx;
ret.mx_id = t2.mx_id;
}
return ret;
}
} st;
void dp() { // 跑一次 dp
for (int i = 2; i <= n; i++) { // 算 f(直接走左邊)
int l = 1, r = i - 1, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (abs(a[mid].x - a[i].x) <= a[i].r) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
if (ans == -1)
continue;
segment t = st.query(1, 1, n, ans, i - 1);
dpl[i] = min(dpl[i],
min((dpl[t.mn_id] == -1 ? LLONG_MAX : dpl[t.mn_id]), ans));
}
for (int i = n - 1; i >= 1; i--) { // 算 g(直接走右邊)
int l = i + 1, r = n, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (abs(a[mid].x - a[i].x) <= a[i].r) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if (ans == -1)
continue;
segment t = st.query(1, 1, n, i + 1, ans);
dpr[i] = max(dpr[i],
max((dpr[t.mx_id] == -1 ? LLONG_MIN : dpr[t.mx_id]), ans));
}
for (int i = 2; i <= n; i++) { // 算 g(走左邊再走右邊)
int l = 1, r = i - 1, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (abs(a[mid].x - a[i].x) <= a[i].r) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
if (ans == -1)
continue;
segment t = st.query(1, 1, n, ans, i - 1);
dpr[i] = max(dpr[i],
max((dpr[t.mx_id] == -1 ? LLONG_MIN : dpr[t.mx_id]), ans));
}
for (int i = n - 1; i >= 1; i--) { // 算 f(走右邊再走左邊)
int l = i + 1, r = n, ans = -1;
while (l <= r) {
int mid = (l + r) / 2;
if (abs(a[mid].x - a[i].x) <= a[i].r) {
ans = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
if (ans == -1)
continue;
segment t = st.query(1, 1, n, i + 1, ans);
dpl[i] = min(dpl[i],
min((dpl[t.mn_id] == -1 ? LLONG_MAX : dpl[t.mn_id]), ans));
}
}
void init() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i].x >> a[i].r;
dpl[i] = dpr[i] = i;
}
}
void solve() {
st.build(1, 1, n); // 建樹
dp(); // 跑兩次
dp();
int ans = 0;
for (int i = 1; i <= n; i++) { // 統計答案
// cout << dpl[i] << " " << dpr[i] << endl;
if (dpl[i] == -1 && dpr[i] == -1) {
ans = 1LL * (ans + 1 * i) % mod;
continue;
}
if (dpl[i] == -1) {
ans = (ans + 1LL * (1LL * (dpr[i] - i + 1) % mod * i) % mod) % mod;
continue;
}
if (dpr[i] == -1) {
ans = (ans + 1LL * (1LL * (i - dpl[i] + 1) % mod * i) % mod) % mod;
continue;
}
ans = (ans + 1LL * (1LL * (dpr[i] - dpl[i] + 1) % mod * i) % mod) % mod;
}
cout << ans;
}
void main() {
init();
solve();
}
} // namespace zqh
謹此,紀念我這道自己想、自己調、並且差點賽切的紫題(賽時 \(92\) 分)。