首页 > 其他分享 >P5025 [SNOI2017] 炸弹 题解

P5025 [SNOI2017] 炸弹 题解

时间:2025-01-12 16:44:20浏览次数:1  
标签:int 题解 mx seg lft SNOI2017 P5025 id dp

题意

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 (Q \times n \log n)\),带一点线段树的并不小的常数(\(Q\) 取决于 \(dp\) 多少次,也就是数据强度,此时 \(Q = 2\))。

如果你们想的话,可以把二分放到外面预处理,省掉了一点点 \(dp\) 的常数。

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

这样速度快得飞起,在 \(n = 500000\) 时都可以在 1820ms 内卡过

标签:int,题解,mx,seg,lft,SNOI2017,P5025,id,dp
From: https://www.cnblogs.com/zphh/p/18474052

相关文章

  • AT_abc388_f Dangerous Sugoroku 题解
    太幽默了。显然可以用矩阵快速幂解决,矩阵里维护距离当前点\(B\)以内的所有点可不可达,转移只需分段,在区间内和不在区间内用不同的转移矩阵即可。复杂度\(O(B^3m\logn)\)。然后你就T了。此时你很急,你现在应该快点卡常来AK这场比赛而不是研究其他的做法,于是我们发现快速幂......
  • Atcoder ABC388F Dangerous Sugoroku 题解 [ 蓝 ] [ 矩阵加速 ] [ 状压矩乘 ] [ 模拟
    DangerousSugoroku:赛时写了矩乘T飞了,受到sunkuangzheng大佬的启发才知道要状压矩乘。暴力矩乘思路直接像过河那样写模拟细节非常多,于是考虑像美食家一样的思路,利用矩阵分段加速。定义\(dp_i\)表示\(i\)能否到达,则有如下转移:\[dp_{i}=\bigvee_{j=i-B}^{i-A}dp_{j}\]......
  • 买房子题解
    【题目要求】问第几年能够买下这套房子。一、建立两个变量,一个表示积攒的钱数,一个表示房价。二、循环20次,判断第i年他能否买下,若能,那么输出i,最后return0。三、在循环外面,输出Impossible。【题解代码】include<bits/stdc++.h>usingnamespacestd;intmain(){double......
  • 整数序列的元素最大跨度值题解
    【题目要求】计算序列的最大跨度值(最大值-最小值)一、求最大值如果a大于最大值,那么最大值就变成a,开始最大值要等于0。二、求最小值如果a小于最小值,那么最小值就变成a,开始最小值要等于1000。【题解代码】include<bits/stdc++.h>usingnamespacestd;intmain(){intn,a,......
  • Atcoder ABC387F Count Arrays 题解 [ 绿 ] [ 基环树 ] [ 树形 dp ] [ 前缀和优化 ]
    CountArrays:一眼秒的计数题。思路显然,把小于等于的条件化为大的向小的连单向边,每个数的入度都是\(1\),就会形成一个基环树森林。那么考虑这个环上能填什么数。因为所有数都小于等于他后面的数,所以所有数都只能相等。这就启发我们在基环树上缩点之后再进行计数。那么当缩完点......
  • 题解:AT_abc388_g [ABC388G] Simultaneous Kagamimochi 2
    鉴于本题解书写时洛谷题面暂无中文翻译,为避免可能的歧义或困惑,先对本题解中的译法进行约定:(顺便吐槽音译怪)英文题面中“mochi”或日文题面中“餅”译为“饼”。英文题面中“kagamimochi”或日文题面中“鏡餅”译为“镜饼”。鉴于本题是C和E的加强版,而我懒得去写那两题的题......
  • 2025/1/11 第25场蓝桥入门赛 题解
    A: 哪来的AC  :  题目链接水题:31画#include<iostream>usingnamespacestd;intmain(){//请在此输入您的代码cout<<31;return0;}B: 酒店安排  : 题目链接 思路:从大到小排序,求每两个相邻房间的差值 ,滑动窗口求m-1个差值最小,即为答案要想最......
  • 题解:P1970 [NOIP2013 提高组] 花匠
    闲话本文同步发布在cnblogs。正题容易发现此题要求花必须一高一低摆放。最优化问题,看不出怎么贪心,遂DP。设计状态\(f_{i,0}\)表示当前为上升形势最长花序列,\(f_{i,1}\)表示当前为下降形势最长花序列。状态转移由于需要一高一低,易得:\[f_{i,0}=\begin{cases}......
  • P3247 [HNOI2016] 最小公倍数 题解
    \(\text{P3247[HNOI2016]最小公倍数题解}\)第一眼上去没什么明显的思路。图上问题一般没有什么好的多项式复杂度算法来解决。观察数据范围,注意到\(n,q\le5\times10^4\),是一个典型的根号复杂度算法,于是考虑分块来处理。注意到所求的不一定是简单路径,也就是在不超过所需要的......
  • P10200 花神诞日 题解
    P10200[湖北省选模拟2024]花神诞日题解首先注意到一个集合中两两异或和的最小值就是,排序后相邻两个数异或和的最小值。证明可以考虑放到01-Trie上,从高往低位建树,求一个数与之异或的最小值,就是使高位相同位数尽可能多,则就是01-Trie上的前一个叶子或后一个叶子。由此,我们......