首页 > 其他分享 >LibreOJ-3038 「JOISC 2019 Day3」穿越时空 Bitaro <线段树> 题解

LibreOJ-3038 「JOISC 2019 Day3」穿越时空 Bitaro <线段树> 题解

时间:2024-04-18 13:34:00浏览次数:26  
标签:3038 lc LibreOJ int 题解 线段 三元组 rc 二元

审题

  1. 一条链

  2. 每条边有通行时间上下界限制

  3. 通过一条边需要 \(1\) 单位时间

  4. 站在当前节点时间减少 \(1\) 耗费 \(1\) 单位代价

  5. \(q\) 次询问

  6. 要么更改一条边的通信时间上下界

  7. 要么询问在 \(b\) 时刻在城市 \(a\),\(d\) 时刻到达城市 \(c\) 的最小代价

思想

做题准备 1

我们尝试将题目直观地表示出来。

一个点(实际上是边)有一个区间,有 \(n-1\) 个点。

我们将他画在坐标系上,区间就是平行于 \(y\) 轴的线段。

虽然对于解法没有帮助,但可以帮助我们更好地思考题目。

拿样例 2 来举个例子:

转化 1

注意到通过一条边需要 \(1\) 单位时间,而肯定不会走回头路。

我们分类讨论,把从左向右查询与从右向左分别做一遍。

我们现在看看样例 2 的从左往右的轨迹是什么样的:

这个轨迹是斜的,很不好维护,怎么办?

把他拉成平的。

我们实际上就是将区间的左右端点都减去了 \(i\),这使得原本斜的线都变成了直线。

中间总结 1

现在我们发现如果没有时间复杂度的要求的话答案肯定是很好求的。

就是在几个线段里面走,上升,向右走不用代价,向下走要代价,要穿过中间的每个区间。

重新看一下 6 和 7。(见审题)

单点修改,区间查询。

上线段树!

步骤 1

我们很明显维护这些线段。

摆在我们面前的问题只有一个:如何合并两堆线段。

首先我们发现如果只维护答案的话,肯定是更新不了的。

我们想想能不能把一堆线段抽象成几个数

我们先考虑最简单的情况,就是样例二的最开始的那张图。

我们如果要从最左边走到最右边那他等价于什么呢?

实际上这些区间的并不为空,这意味着,我们只要先等到或回退到这个区间的并内,就可以一条路走到底

如图:

具体的话那很明显肯定是就近走到最近的在并内的点。

那还有一种情况,区间的并不为空:

这个时候,我们将最优路线画出来:

图中黑色虚线即为路径。

发现一点,我们无论从左侧哪个点开始,最优方案总是会先汇聚到一个固定的点,再走到一个固定的点,然后再发散到终点。

所以,我们就可以将一个区间由以下两种表示方式中的一种来表示:

  • 这段区间的并(后文称之为二元组)

  • 汇聚到了哪个点,中间花费多少代价,最终走到哪个点(后文称之为三元组)

步骤 2

我们现在想想如何合并。

分类讨论。

二元组+二元组

我们分两种情况:

  • \(1^{\circ}\) 他们的区间并不为空

    我们直接将答案变成二元组,该二元组为原二元组的并

  • \(2^{\circ}\) 他们的区间并为空

    第一种情况,左线段在右线段上面

    那我们就先汇聚到左线段的左端点,再走到右线段的右端点,代价为左线段左端点的时间与右线段右端点的时间差。

    第二种情况,左线段在右线段下面

    同理,但代价为 \(0\)。

二元组+三元组&三元组+二元组

本质等价,这里只讲二元组+三元组。

如果三元组的起点在二元组区间内,那答案三元组与原三元组一致,相当于先走到原三元组起点的时间再一路走到底,再走三元组。

如果三元组的起点在二元组区间以上,那答案三元组的起点为二元组右端点,其他与原三元组一致,相当于贴着上限走,一过了二元组就升到三元组起点位置然后走三元组。

如果三元组的起点在二元组区间之下,那答案三元组起点为二元组左端点,终点为原三元组终点,代价为二元组左端点与原三元组起点的时间差,相当于贴着下限走,一过了二元组就回到三元组起点位置然后走三元组。

三元组+三元组

这个比较简单,就是直接首位相接,代价相加,代价还要加上第一个三元组的终点到第二个三元组的起点的代价。

代码

小技巧 1

可以写一个维护二元组或三元组的结构体,然后 pushup 就变成一个输入两个结构体,输出一个结构体的函数。

这样便于将询问时把两端区间的结果合并到一起。

小技巧 2

可以写一个 solve 函数来求解单向的答案,这样双向只要 reverse 一下就可以求解了。

真正的代码

#include <bits/stdc++.h>
using namespace std;

constexpr int MAXN = 3e5 + 5;

int l[MAXN], r[MAXN], tmpl[MAXN], tmpr[MAXN];
struct Data {
    int x, y;
    long long z;

    Data(int _x = 0, int _y = 0, long long _z = -1) {
        x = _x;
        y = _y;
        z = _z;
    }
};

Data pushup(Data lc, Data rc) {
    if (~lc.z && ~rc.z)
        return Data(lc.x, rc.y, lc.z + rc.z + max(lc.y - rc.x, 0));
    if (~lc.z)
        return Data(lc.x, max(min(lc.y, rc.y), rc.x), lc.z + max(lc.y - rc.y, 0));
    if (~rc.z)
        return Data(min(max(lc.x, rc.x), lc.y), rc.y, rc.z + max(lc.x - rc.x, 0));
    if (max(lc.x, rc.x) <= min(lc.y, rc.y))
        return Data(max(lc.x, rc.x), min(lc.y, rc.y));
    if (lc.y < rc.x)
        return Data(lc.y, rc.x, 0);
    return Data(lc.x, rc.y, lc.x - rc.y);
}

struct SegTree {

    struct Node {
        int l, r;
        Data data;
    } tr[MAXN * 4];

    void build(int k, int lp, int rp) {
        tr[k] = { lp, rp, Data(l[lp], r[lp]) };
        if (lp == rp)
            return;
        int mid = lp + rp >> 1, lc = k * 2, rc = k * 2 + 1;
        build(lc, lp, mid);
        build(rc, mid + 1, rp);
        tr[k].data = pushup(tr[lc].data, tr[rc].data);
    }

    void update(int k, int p, Data v) {
        if (tr[k].l == tr[k].r) {
            tr[k].data = v;
            return;
        }
        int mid = tr[k].l + tr[k].r >> 1, lc = k * 2, rc = k * 2 + 1;
        if (p <= mid)
            update(lc, p, v);
        else
            update(rc, p, v);
        tr[k].data = pushup(tr[lc].data, tr[rc].data);
    }

    Data query(int k, int l, int r) {
        if (l <= tr[k].l && tr[k].r <= r)
            return tr[k].data;
        int mid = tr[k].l + tr[k].r >> 1;
        int lc = k * 2, rc = k * 2 + 1;
        if (r <= mid)
            return query(lc, l, r);
        else if (l > mid)
            return query(rc, l, r);
        else
            return pushup(query(lc, l, mid), query(rc, mid + 1, r));
    }
} segtree;

int n, qq;

struct Query {
    int opt, a, b, c, d;
    Data ans;
} q[MAXN];

void solve() {
    if (n == 1)
        return;
    for (int i = 1; i < n; i++)
        l[i] = tmpl[i] - i, r[i] = tmpr[i] - i - 1;
    segtree.build(1, 1, n - 1);
    for (int i = 1; i <= qq; i++) {
        if (q[i].opt == 1) {
            segtree.update(1, q[i].a, Data(q[i].b - q[i].a, q[i].c - 1 - q[i].a));
        } else if (q[i].a < q[i].c) {
            q[i].ans = Data(q[i].b - q[i].a, q[i].b - q[i].a);
            q[i].ans = pushup(q[i].ans, segtree.query(1, q[i].a, q[i].c - 1));
            q[i].ans = pushup(q[i].ans, Data(q[i].d - q[i].c, q[i].d - q[i].c));
        }
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> qq;
    for (int i = 1; i < n; i++)
        cin >> tmpl[i] >> tmpr[i];

    for (int i = 1; i <= qq; i++) {
        cin >> q[i].opt >> q[i].a >> q[i].b >> q[i].c;
        if (q[i].opt == 2)
            cin >> q[i].d;
    }

    solve();
    reverse(tmpl + 1, tmpl + n);
    reverse(tmpr + 1, tmpr + n);

    for (int i = 1; i <= qq; i++) {
        if (q[i].opt == 1) {
            q[i].a = n - q[i].a;
        } else {
            q[i].a = n - q[i].a + 1;
            q[i].c = n - q[i].c + 1;
        }
    }

    solve();

    for (int i = 1; i <= qq; i++) {
        if (q[i].opt == 2) {
            if (q[i].a == q[i].c) {
                cout << max(q[i].b - q[i].d, 0) << endl;
            } else {
                cout << q[i].ans.z << endl;
            }
        }
    }

        return 0;
}

标签:3038,lc,LibreOJ,int,题解,线段,三元组,rc,二元
From: https://www.cnblogs.com/LightningCreeper/p/18143327

相关文章

  • 前端面试题解析与总结
    在2024年的前端行业,面试是进入理想公司的一道门槛。不同公司的面试流程和考察点各有不同,下面将结合三家知名公司的面试题目进行分析和总结,为广大前端开发者提供一份参考指南。一、某对外电商一面:笔试题:弹窗组件防抖截流代码实现关系型数组转换成树形结构对象数组全排列......
  • at_cf17final_j Tree MST 题解
    题目链接点击打开链接题目解法还是挺有收获的题解法1完全图求\(mst\),首先应该考虑\(boruvka\)算法现在的问题转化成了求\(O(\logn)\)次每个点\(x\)到不在\(x\)的连通块中的点的最短边这个可以换根\(dp\)求子树内的是好求的,只要记录所有连通块的最小值和次小值......
  • mongodb启动失败问题解决
    解决办法sudochown-Rmongod:mongod/var/lib/mongochown-Rmongod:mongod/var/log/mongodb/chown-Rmongod:mongod/tmp/mongodb-27017.sock或者可以使用mongod--config/etc/mongod.conf参考:MongoDBloadsbutbreaks,returningstatus=14-AskUbuntumon......
  • AT_abc211_d [ABC211D] Number of Shortest paths 题解
    题目简述给定一张$n$个点$m$条边的无向无权图,问从$1$到$n$的最短路有多少条。题目分析设$cnt_i$表示从$1$到$i$的最短路条数,$dis_i$表示最短路。这道题可以考虑使用BFS做,对于一个点$v$,设第一次更新它的点为$u$,则它的转移应为$cnt_v\leftarrowcnt_u$并......
  • CF81C Average Score 题解
    题目简述给定一个长度为$n$的序列,在其中取出$x$个数,构成一个数列$a$,剩下的$y$个数构成数列$b$。若第$i$个数在数列$a$中,$ans_i$等于$1$,否则等于$2$,请你给出一种方案使得两数列的平均数之和最大且$ans$的字典序最小.题目分析我们先考虑$x=y$的情况,在这种情......
  • ICPC2023杭州站题解(B D E F G H J M)
    本场金牌数量较其他场多(45枚),但金牌线题数不多。五题为分水岭,五道简单题过后所有题均为金牌题,其中有四道可做,即ABEF,做出任意一道即可拿金牌。这里提供除A题以外的所有可做题的题解。ICPC2023杭州站:M:加入比当前选择的所有数大的数一定会让平均值上升,因此答案数列中,V图中的......
  • [题解][洛谷P1136] 迎接仪式
    题目描述对于一个由字母“j”和“z”组成的字符串,可以任意交换两个字符的位置不超过k次,求最多能出现多少个“jz”字串。题解动态规划题。设f[i][j][k][0/1]表示到第i位,前面交换了j个“j”,交换了k个“z”,且第i位是j(用0表示)或z(用1表示)。当j=k时即为可行解。为什么需要用第四维......
  • [题解][洛谷P1108] 低价购买
    题目描述求最长下降子序列长度,以及最长下降子序列的个数。(构成的序列一样的时候,视为同一种最长下降子序列)题解n不超过5000,n^2复杂度即可解决该问题。主要在于如何统计最长下降子序列个数。可以设数组t[i]表示以i为结尾的最长下降子序列个数,在更新f[i]的时候顺便更新。t[i]=......
  • [ABC229E] Graph Destruction 题解
    [ABC229E]GraphDestruction题解思路解析题目要求删点,而众所周知删点的代价要大于加点的代价,于是我们考虑倒着处理询问,将每一个删点改成加点,而加点时就用并查集维护连通块即可。code#include<bits/stdc++.h>usingnamespacestd;constintN=2e5+10;intn,m,fa[N];......
  • P10342 [THUSC 2019] 数列 题解
    形式化题面:求\[\sum_{l=1}^{n}\sum_{r=l}^{n}\max_{i=l}^{r}(i-l+1)\timesf(i,r)\]其中\(f(l,r)\)为\(a_l,...,a_r\)中有多少个不同的数字。注意到,除了Sub2,其余数据点都有\(\maxf\le800\),这启发我们考虑\(O(nm)\)的算法。套路地,扫描线枚举右端点,则现在只需要考虑......