F - Takahashi in Narrow Road

F - Takahashi in Narrow Road

Problem Statement

There is a road extending east and west, and N persons are on the road. The road extends infinitely long to the east and west from a point called the origin.

The i-th person (1iN) is initially at a position Xi meters east from the origin.

The persons can move along the road to the east or west. Specifically, they can perform the following movement any number of times.

  • Choose one person. If there is no other person at the destination, move the chosen person 1 meter east or west.

They have Q tasks in total, and the i-th task (1iQ) is as follows.

  • The Ti-th person arrives at coordinate Gi.

Find the minimum total number of movements required to complete all Q tasks in order.

Constraints

  • 1N2×105
  • 0X1<X2<<XN108
  • 1Q2×105
  • 1TiN (1iQ)
  • 0Gi108 (1iQ)
  • All input values are integers.

Input

The input is given from Standard Input in the following format:

N
X1 X2 XN
Q
T1 G1
T2 G2

TQ GQ

Output

Print the answer.


Sample Input 1

5
10 20 30 40 50
4
3 45
4 20
1 35
2 60

Sample Output 1

239

An optimal sequence of movements for the persons is as follows (the positions of the persons are not necessarily drawn to scale):

For each task, the persons move as follows.

  • The 4th person moves 6 steps east, and the 3rd person moves 15 steps east.
  • The 2nd person moves 2 steps west, the 3rd person moves 26 steps west, and the 4th person moves 26 steps west.
  • The 4th person moves 18 steps east, the 3rd person moves 18 steps east, the 2nd person moves 18 steps east, and the 1st person moves 25 steps east.
  • The 5th person moves 13 steps east, the 4th person moves 24 steps east, the 3rd person moves 24 steps east, and the 2nd person moves 24 steps east.

The total number of movements is 21+54+79+85=239.

You cannot complete all tasks with a total movement count of 238 or less, so print 239.


Sample Input 2

8
0 1 2 3 4 5 6 100000000
6
1 100000000
8 0
1 100000000
8 4
1 100000000
5 21006578

Sample Output 2

4294967297

Note that some persons may need to move to the west of the origin or more than 108 meters to the east of it.

Also, note that the answer may exceed 232.


Sample Input 3

12
1558 3536 3755 3881 4042 4657 5062 7558 7721 8330 8542 9845
8
9 1694
7 3296
12 5299
5 5195
5 5871
1 2491
8 1149
8 2996

Sample Output 3

89644

 

解题思路

  官方题解看不懂,只能按自己比较复杂的思路去做,结果写代码加调试用了差不多 4 个小时。提示,本篇题解会用到支持区间赋值与区间加等差数列的线段树。区间赋值的内容可以参考:扶苏的问题。区间加等差数列的内容可以参考:E. Space Harbour

  其实只要按照题意去模拟就能得到答案,难的地方就在于,每次将一个人移到目标位置时哪些人的位置也会受到影响?如何去维护这个过程中受影响的人的位置?用 pt 表示第 t 给人的位置,下面只讨论 pt<g 的情况,也就是第 t 个人要往右移动到目标位置。处理向左移动情况的思想是一样的,类比一下就好了。

  首先要解决第一个问题,第 t 个人从 pt 移动到 g 的过程中右边有多少个人也会移动。容易知道这些人的下标必定是连续的一段,我们不妨假设有 c 个人(包括第 t 个人),那么最后的结果就是 {ptgpt+1g+1pt+ig+i1pt+c1g+c1。可以用二分找到这个 c,这是因为受影响的下标是连续的一段,因此具有二段性。对于二分点 m,check 的时候检查 pt+m1 是否小于等于 g+m1(等于 gm+1 的时候并不会影响答案),如果是的话呢,说明第 t+m1 个人会受到影响,否则不会受到影响。

  现在我们找到一段会受到影响的连续位置了,那么这个移动过程中对答案的贡献就是

(gpt)+(g+1pt+1)++(g+c1pt+c1)=c(g+gc+1)2i=tt+c1pi

  所以现在的问题是我们该如何快速求出 i=tt+c1pi,以及更新 pi

  显然我们可以用线段树去维护序列 pi,那么 i=tt+c1pi 就是简单的区间查询。而更新就很复杂了,由于我们不可能一个一个去修改,因此首先需要将 ptpt+c1 先赋值为 g,然后再加上一个首相为 0 公差为 1 的等差数列。所以实际上我们需要维护的是 pi 的差分数组,定义差分数组 fi=pipi1

  与一般的支持区间加等差数列的线段树维护的东西一样,唯一不同的地方在于懒标记。除了区间加的懒标记 add 外,还需要维护区间赋值的懒标记 tag。其中当 tag 时表示没有区间赋值的操作,不用下传。

  为了简化操作这里将区间赋值和区间加等差数列统一成一个操作,表示成函数 add(int l, int r, int c, int k, int d),意思是对区间 [l,r] 内的元素先赋值为 c,再加上首相为 k 公差为 d 的等差数列,函数内容为下:

void add(int l, int r, int c, int k, int d) {
    if (c != INF) {
        modify(1, r + 1, r + 1, query(r + 1, r + 1) - c, 0);
        modify(1, l, l, c - query(l - 1, l - 1), 0);
        modify(1, l + 1, r, 0, 0);
    }
    else {
        modify(1, l, l, c, k);
        modify(1, l + 1, r, c, d);
        modify(1, r + 1, r + 1, c, -(k + (r - l) * d));
    }
}

  这部分是对上面函数的解释。规定当 c 表示需要进行赋值操作,由于线段树维护的是差分数组,我们要考虑赋值对差分数组的影响是怎样的。对 plpr 进行赋值,flfr+1 会受到影响。首先 pl 被修改成 c,自然有 flcpl1,其中 query(l,r) 得到的是 i=lrpi,所以我们需要将 fl 赋值成 cpl1modify(int u, int l, int r, LL c, LL d) 的含义就是将区间 [l,r] 内的元素先赋值为 c 后再加上 d。然后是 fl+1fr,由于 plpr 的值都是 c,因此需要将 fl+1fr 都赋值为 0。最后是需要将 fr+1 赋值成 pr+1c。代码中的顺序会不一样是为了防止修改操作对查询的影响。

  然后就是对区间 [l,r] 加上等差数列了,与一般的支持区间加等差数列一样这里就不过多赘述了,包括 modify 的具体内容和懒标记的更新也与一般的支持区间赋值的线段树一样。

  所以对于一个过程,二分出 c 后,需要先后调用 add(t, t + c - 1, g, 0, 0) 和 add(t, t + c - 1, INF, 0, 1) 来维护受影响的人的位置。

  AC 代码如下,时间复杂度为 O(qlog2n)

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

typedef long long LL;

const int N = 2e5 + 5, INF = 0x3f3f3f3f;

int a[N];
struct Node {
    int l, r;
    LL s1, s2, tag, add;
}tr[N * 4];

void pushup(int u) {
    tr[u].s1 = tr[u << 1].s1 + tr[u << 1 | 1].s1;
    tr[u].s2 = tr[u << 1].s2 + tr[u << 1 | 1].s2;
}

void build(int u, int l, int r) {
    tr[u] = {l, r, 0, 0, INF, 0};
    if (l == r) {
        tr[u].s1 = a[l] - a[l - 1];
        tr[u].s2 = (l - 1) * tr[u].s1;
    }
    else {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void upd(int u, LL c, LL d) { // 将区间[l,r]内的元素赋值为c后再对每个元素加上d
    // 如果c=INF意味着没有赋值操作,否则需要将区间内的元素均赋值为c
    if (c != INF) {
        tr[u].s1 = (tr[u].r - tr[u].l + 1ll) * c;
        tr[u].s2 = (tr[u].r - tr[u].l + 1ll) * (tr[u].l + tr[u].r - 2) / 2 * c;
        tr[u].tag = c;
        tr[u].add = 0;
    }
    // 再对区间内的元素均加上d
    tr[u].s1 += (tr[u].r - tr[u].l + 1) * d;
    tr[u].s2 += (tr[u].r - tr[u].l + 1ll) * (tr[u].l + tr[u].r - 2) / 2 * d;
    tr[u].add += d;
}

void pushdown(int u) {
    upd(u << 1, tr[u].tag, tr[u].add);
    upd(u << 1 | 1, tr[u].tag, tr[u].add);
    tr[u].tag = INF, tr[u].add = 0;
}

void modify(int u, int l, int r, LL c, LL d) {
    if (l > r) return;
    if (tr[u].l >= l && tr[u].r <= r) {
        upd(u, c, d);
    }
    else {
        pushdown(u);
        int mid = tr[u].l + tr[u].r >> 1;
        if (l <= mid) modify(u << 1, l, r, c, d);
        if (r >= mid + 1) modify(u << 1 | 1, l, r, c, d);
        pushup(u);
    }
}

LL query(int u, int l, int r) { // 查询差分数组在区间[l,r]的和,一般l=1,则query(u,1,r)的结果为第r个元素的值
    if (l > r) return 0;
    if (tr[u].l >= l && tr[u].r <= r) return r * tr[u].s1 - tr[u].s2;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (r <= mid) return query(u << 1, l, r);
    if (l >= mid + 1) return query(u << 1 | 1, l, r);
    return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}

LL query(int l, int r) { // 查询原数组在区间[l,r]的元素和
    return query(1, 1, r) - query(1, 1, l - 1);
}

void add(int l, int r, int c, int k, int d) { // 将区间[l,r]内的元素赋值为c后再对该区间加上首相为k公差为d的等差数列
    if (c != INF) {
        modify(1, r + 1, r + 1, query(r + 1, r + 1) - c, 0);
        modify(1, l, l, c - query(l - 1, l - 1), 0);
        modify(1, l + 1, r, 0, 0);
    }
    else {
        modify(1, l, l, c, k);
        modify(1, l + 1, r, c, d);
        modify(1, r + 1, r + 1, c, -(k + (r - l) * d));
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n, m;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    build(1, 1, n + 1);
    cin >> m;
    LL ret = 0;
    while (m--) {
        int x, y;
        cin >> x >> y;
        if (query(x, x) < y) {
            int l = 1, r = n - x + 1;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                if (y + mid - 1 >= query(x + mid - 1, x + mid - 1)) l = mid;
                else r = mid - 1;
            }
            ret += l * (y + y + l - 1ll) / 2 - query(x, x + l - 1);
            add(x, x + l - 1, y, 0, 0);
            add(x, x + l - 1, INF, 0, 1);
        }
        else {
            int l = 1, r = x;
            while (l < r) {
                int mid = l + r + 1 >> 1;
                if (y - mid + 1 <= query(x - mid + 1, x - mid + 1)) l = mid;
                else r = mid - 1;
            }
            ret += query(x - l + 1, x) - l * (y - l + 1ll + y) / 2;
            add(x - l + 1, x, y - l + 1, 0, 0);
            add(x - l + 1, x, INF, 0, 1);
        }
    }
    cout << ret;
    
    return 0;
}

 

参考资料

  Editorial - AtCoder Beginner Contest 371:https://atcoder.jp/contests/abc371/editorial/10931

posted @   onlyblues  阅读(48)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示