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 $(1\leq i\leq N)$ is initially at a position $X_i$ 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 $(1\leq i\leq Q)$ is as follows.
- The $T_i$-th person arrives at coordinate $G_i$.
Find the minimum total number of movements required to complete all $Q$ tasks in order.
Constraints
- $1\leq N\leq2\times10^5$
- $0\leq X_1 < X_2 < \dotsb < X_N \leq10^8$
- $1\leq Q\leq2\times10^5$
- $1\leq T_i\leq N\ (1\leq i\leq Q)$
- $0\leq G_i\leq10^8\ (1\leq i\leq Q)$
- All input values are integers.
Input
The input is given from Standard Input in the following format:
$N$
$X_1$ $X_2$ $\ldots$ $X_N$
$Q$
$T_1$ $G_1$
$T_2$ $G_2$
$\vdots$
$T_Q$ $G_Q$
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 $10^8$ meters to the east of it.
Also, note that the answer may exceed $2^{32}$.
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。
其实只要按照题意去模拟就能得到答案,难的地方就在于,每次将一个人移到目标位置时哪些人的位置也会受到影响?如何去维护这个过程中受影响的人的位置?用 $p_t$ 表示第 $t$ 给人的位置,下面只讨论 $p_t < g$ 的情况,也就是第 $t$ 个人要往右移动到目标位置。处理向左移动情况的思想是一样的,类比一下就好了。
首先要解决第一个问题,第 $t$ 个人从 $p_t$ 移动到 $g$ 的过程中右边有多少个人也会移动。容易知道这些人的下标必定是连续的一段,我们不妨假设有 $c$ 个人(包括第 $t$ 个人),那么最后的结果就是 $\displaylines{\begin{cases} p_t \gets g \\ p_{t+1} \gets g+1 \\ \vdots \\ p_{t+i} \gets g + i - 1 \\ \vdots \\ p_{t + c - 1} \gets g + c - 1 \end{cases}}$。可以用二分找到这个 $c$,这是因为受影响的下标是连续的一段,因此具有二段性。对于二分点 $m$,check 的时候检查 $p_{t+m-1}$ 是否小于等于 $g+m-1$(等于 $g-m+1$ 的时候并不会影响答案),如果是的话呢,说明第 $t+m-1$ 个人会受到影响,否则不会受到影响。
现在我们找到一段会受到影响的连续位置了,那么这个移动过程中对答案的贡献就是
$$(g - p_t) + (g+1 - p_{t+1}) + \cdots + (g+c-1 - p_{t+c-1}) = \frac{c \cdot (g + g-c+1)}{2} - \sum\limits_{i=t}^{t+c-1}{p_{i}}$$
所以现在的问题是我们该如何快速求出 $\sum\limits_{i=t}^{t+c-1}{p_{i}}$,以及更新 $p_i$。
显然我们可以用线段树去维护序列 $p_i$,那么 $\sum\limits_{i=t}^{t+c-1}{p_{i}}$ 就是简单的区间查询。而更新就很复杂了,由于我们不可能一个一个去修改,因此首先需要将 $p_t \ldots p_{t+c-1}$ 先赋值为 $g$,然后再加上一个首相为 $0$ 公差为 $1$ 的等差数列。所以实际上我们需要维护的是 $p_i$ 的差分数组,定义差分数组 $f_i = p_i - p_{i-1}$。
与一般的支持区间加等差数列的线段树维护的东西一样,唯一不同的地方在于懒标记。除了区间加的懒标记 add
外,还需要维护区间赋值的懒标记 tag
。其中当 tag
是 $\infty$ 时表示没有区间赋值的操作,不用下传。
为了简化操作这里将区间赋值和区间加等差数列统一成一个操作,表示成函数 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 \ne \infty$ 表示需要进行赋值操作,由于线段树维护的是差分数组,我们要考虑赋值对差分数组的影响是怎样的。对 $p_l \sim p_r$ 进行赋值,$f_l \sim f_{r+1}$ 会受到影响。首先 $p_l$ 被修改成 $c$,自然有 $f_l \gets c - p_{l-1}$,其中 query(l,r)
得到的是 $\sum\limits_{i=l}^{r}{p_i}$,所以我们需要将 $f_l$ 赋值成 $c - p_{l-1}$,modify(int u, int l, int r, LL c, LL d)
的含义就是将区间 $[l,r]$ 内的元素先赋值为 $c$ 后再加上 $d$。然后是 $f_{l+1} \sim f_{r}$,由于 $p_{l} \sim p_{r}$ 的值都是 $c$,因此需要将 $f_{l+1} \sim f_{r}$ 都赋值为 $0$。最后是需要将 $f_{r+1}$ 赋值成 $p_{r+1} - c$。代码中的顺序会不一样是为了防止修改操作对查询的影响。
然后就是对区间 $[l,r]$ 加上等差数列了,与一般的支持区间加等差数列一样这里就不过多赘述了,包括 modify
的具体内容和懒标记的更新也与一般的支持区间赋值的线段树一样。
所以对于一个过程,二分出 $c$ 后,需要先后调用 add(t, t + c - 1, g, 0, 0)
和 add(t, t + c - 1, INF, 0, 1)
来维护受影响的人的位置。
AC 代码如下,时间复杂度为 $O(q\log^{2}{n})$:
#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
标签:east,leq,int,moves,person,steps,Narrow,Road,Takahashi From: https://www.cnblogs.com/onlyblues/p/18429331