P5025 [SNOI2017] 炸彈 題解

_zqh發表於2024-10-18

題意

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)\) 最小。

此時就可以轉移了:

\[f(i) = \min \{ f(t), l\} \]

但找出 \(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\) 分)。

相關文章