[HNOI 2018]转盘
Description
在一个环上有 $n$ 个物品,第 $i$ 个物品的出现时间为 $T_i$ 。一开始你可以任意选择一个物品的位置作为起始位置,然后以这个位置为起点沿正方向走,走一个单位花一个单位的时间,不能调头,可以停留。问至少多少时间可以取完所有的物品。一个物品 $i$ 能被取当且仅当到达该物品的位置时时间 $\geq T_i$ ,初始时间为 $0$ 。支持 $m$ 次单点修改,强制在线。
$3\leq n\leq 10^5,0\leq m\leq 10^5$
Solution
推推性质,容易得到这样的一个结论:不会走到相同的地方两次。换句话说就是相当于选定起点之后,每走到一个位置一定会等到该位置的物品出现取上该物品后再继续走。
感性证明下:考虑为什么会有不符合上述情况的走法,显然是一个离我较远的物品比离我较近的物品先出现,并且早的多,那么我就需要先去取较远的物品再走一圈回来取这个出现时间较晚的物品。不过这样的话,我可以直接把起点放在那个较远的物品处,显然答案不会比我这样走的答案差。
既然有这样的结论,我们先将数组倍长,可以枚举起点,再在起点向右枚举 $n$ 个,统计一下最大值,最后取个最小值即可。不过这样单次操作就是 $O(n^2)$ 的,肯定过不了...
考虑到答案就是求:
$$\min_{1\leq i\leq n}\left{\max_{i\leq j\leq i+n-1}\left{T_j+(n-1-(j-i))\right}\right}$$
其实这个是和式子
$$\min_{1\leq i\leq n}\left{\max_{i\leq j\leq 2n}\left{T_j+(n-1-(j-i))\right}\right}$$
是等价的。
将式子变一下形,记 $a_i=T_i-i$
$$\min_{1\leq i\leq n}\left{\max_{i\leq j\leq 2n}\left{a_j\right}+i\right}+n-1$$
考虑如何维护这个东西。
我们让线段树每个节点 $o$ (控制区间为 $[l,r]$ )维护两个值 $maxn_o$ 和 $val_o$ 。表示区间 $a_i$ 最值和 $\min\limits_{l\leq i\leq mid}\left{\max\limits_{i\leq j\leq r}\left{a_j\right}+i\right}$ 。
考虑如何维护这个 $val_o$ 。
对于每个线段树中的非叶子节点,我们需要合并左右儿子的信息。主要的就是考虑右儿子的值会对左儿子中的起点产生影响。
我们不妨记当前节点的右儿子的 $maxn$ 为 $mx$ 。
对于左儿子 $ls$ ,如果他的右儿子的 $maxn\geq mx$ ,显然 $ls$ 的左儿子的 $val$ 值可以直接用;右儿子无法确定,递归处理右儿子。
如果 $ls$ 的右儿子的 $maxn\leq mx$ ,显然 $ls$ 的右儿子的内起点的最小值一定是 $mid+1+mx$ ;左儿子无法确定,递归处理左儿子。
显然对于每次更新需要 $\log$ 的递归询问。
至于更新,只要更新线段树一条链上的所有节点。线段树维护的总复杂度为 $O(n\log_2^2 n)$ 。
Code
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5+5;
int n, m, p, t[N], a[N], x, y, last;
struct Segment_tree {
#define lr(o) (o<<1)
#define rr(o) (o<<1|1)
int maxn[N<<2], val[N<<2];
int query(int o, int l, int r, int mx) {
if (l == r) return l+max(mx, maxn[o]); int mid = (l+r)>>1;
if (mx <= maxn[rr(o)]) return min(val[o], query(rr(o), mid+1, r, mx));
else return min(mid+mx+1, query(lr(o), l, mid, mx));
}
void pushup(int o, int l, int mid, int r) {
val[o] = query(lr(o), l, mid, maxn[rr(o)]);
maxn[o] = max(maxn[lr(o)], maxn[rr(o)]);
}
void build(int o, int l, int r) {
if (l == r) {maxn[o] = a[l], val[o] = l+a[l]; return; }
int mid = (l+r)>>1;
build(lr(o), l, mid), build(rr(o), mid+1, r);
pushup(o, l, mid, r);
}
void update(int o, int l, int r, int loc) {
if (l == r) {maxn[o] = a[l], val[o] = l+a[l]; return; }
int mid = (l+r)>>1;
if (loc <= mid) update(lr(o), l, mid, loc);
else update(rr(o), mid+1, r, loc);
pushup(o, l, mid, r);
}
}T;
void work() {
scanf("%d%d%d", &n, &m, &p);
for (int i = 1; i <= n; i++)
scanf("%d", &t[i]), a[i] = t[i]-i, a[i+n] = t[i]-n-i;
T.build(1, 1, n<<1);
printf("%d\n", last = T.val[1]+n-1);
while (m--) {
scanf("%d%d", &x, &y); x ^= last*p, y ^= last*p;
t[x] = y, a[x] = t[x]-x, a[x+n] = t[x]-n-x;
T.update(1, 1, n<<1, x), T.update(1, 1, n<<1, x+n);
printf("%d\n", last = T.val[1]+n-1);
}
}
int main() {work(); return 0; }