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

P5025 [SNOI2017] 炸弹 题解

时间:2024-10-18 13:33:37浏览次数:1  
标签:int 题解 rht mx seg lft SNOI2017 P5025 id

题意

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\) 分)。

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

相关文章

  • AT_nikkei2019_2_qual_c 题解
    blog。不会做结论题,怎么办???不会做结论题,怎么办???不会做结论题,怎么办???不妨对\(b\)排序,将\(a\)对应到相应的位置。那么题目有两个条件:#1:\(\foralla_i\leb_i\)。#2:操作限制。注意到\(n-1\)次操作就能完成对\(a\)排序。所以用\(n-2\)次操作可以将\(a\)变成一个Almos......
  • P9351 [JOI 2023 Final] Maze 题解
    Description给定一张\(R\timesC\)的地图,其中.可以走,而#不能走。一次操作可以将\(N\timesN\)的正方形范围内所有点变成.,给定起点和终点,求最少需要几次操作使得起点和终点连通(只能上下左右移动)。\(R\timesC\le6\times10^6\),\(N\leR\leC\)。Solution先考虑怎么......
  • CF571B-题解
    CF571B题意给定数组\(A\)和值\(k\),你可以重排\(A\)中的元素,使得\(\displaystyle\sum_{i=1}^{n-k}|A_i-A_{i+k}|\)最小。输出最小值。思路\(A_i,A_{i+k}\)就等同于在将\(i\)模\(k\)的意义上把\(A\)分为若干组贪心的想要使\(\displaystyle\sum_{i=1}^{n-k}|A_i-A......
  • 【题解】Solution Set - NOIP2024集训Day56 哈希杂题
    【题解】SolutionSet-NOIP2024集训Day56哈希杂题https://www.becoder.com.cn/contest/5640「CF568C」NewLanguage做过的2-sat。「NOI2024」集合做过。做法见提交记录。「CSP-S2022」星战简要题意:给定有向图。修改使一条边失效/恢复;使一个点的所有入边......
  • [YDOI R1] Necklace 题解
    题目传送门前置芝士二项式定理:\((a+b)^n=\sum\limits_{i=0}^{n}C^i_n\timesa^i\timesb^{n-i}\)快速幂Meaning有\(n\)种珠子,每种有\(a_i\)颗,且美丽值为\(v_i\)。任意两颗珠子不同(同种类也算不同)。每种珠子有一个漂亮值\(v_i\)。项链有一个美丽度,若第\(i\)种珠子......
  • Codeforces Round 893 (Div. 2)题解记录
    题目链接:https://codeforces.com/contest/1858A.Buttons从自身角度出发,我想赢就得保证我的按钮尽可能多所以,大家都优先按\(c\),然后分析先后顺序即可 #include<iostream> #include<string.h> #include<map> #include<vector> #include<set> #include<unordered_set> #......
  • [ABC134F] Permutation Oddness 题解
    [ABC134F]PermutationOddness题解朴素的想法显然是状压dp,枚举选择的子集,但复杂度不可接受。考虑优化。注意到对于\(p_i\),它的贡献只会有两种可能性,\(+p_i,-p_i\)。于是初步的想法是按照\(p_i\)的正负性选择分类。考虑到对于相同正负性的选择\(p\),其是等价的。于是我们......
  • P4229 某位歌姬的故事 题解
    P4229某位歌姬的故事题解\(n\le9\times10^8\),显然复杂度不与\(n\)相关。\(m\le500\),显然可以接受\(O(Tm^2)\)的做法。对于\([l,r]\),考虑套路地将端点离散化,使得复杂度只和关键点个数有关。考虑对于\([l,r,m]\),离散化后被分成了\(a_1,a_2,\cdots,a_p\)段,那么这些段的......
  • ZZJC新生训练赛第四场题解
    ZZJCACM新生训练赛-2024.10.16题目难度Easy(简单):B,C,D,GMedium(中等):A,EAnti-AK(防AK):EC题解题思路A页既可以是彩印也可以是黑白印,B页只能是彩印,所以只要比较A页彩印和A页黑白印的价格高低就好。因为a,b,x,y最大都是1e9,用int直接相乘的话会爆掉,所以......
  • DMA连续发送多帧但是只有最后一帧数据发出问题解决方法
    问题描述DMA连续发送多帧但是只有最后一帧数据发出原因分析DMA发送未完成时,下次DMA请求启动,导致之前的数据被放弃传输了解决办法创建DMA发送缓冲区,当启动DMA请求的时候,检测DMA设备是不是正在忙,如果正在忙,就把数据放入发送缓冲区等待,上次DMA发送完成的时会产生DMA发送完......