[HNOI/AHOI2018]转盘

首先思考一下这个问题放到序列上怎么做,即:从 (1) 开始只能往右走标记完所有点的最小时间。

首先可以令标记完前 (i) 个点的最小时间为 (f_i),不难发现转移:(f_i = max(T_i, f_{i - 1} + 1))

(+1) 提出来,则有 (f_i = max(T_i - 1, f_{i - 1}) + 1)

于是这可以看作是对于 (T_i - 1)(max),那么就可以改写转移方程为:(f_i = maxlimits_{j = 1} ^ i {T_j} + i - j)

不妨设 (A_i = T_i - i),则可知 (f_i = maxlimits_{j = 1} ^ i A_j + i)

则答案 (f_n = maxlimits_{i = 1} ^ n A_i + n),支持修改使用 ( m set) 或权值线段树均可。


回到原问题,你会发现这个问题和序列上的问题区别在于:可从不为 (1) 的点出发;可以绕多次圈。

但是基于观察可以发现,对于后者实质上是不优的,因为我们可以考虑构造一组等价的不绕圈的方案。

比如从 (S) 出发绕了若干圈到达 (E),令最终的时间为 (T)

则可以考虑在 (E) 处等待 (T - n) 秒,然后反着绕一圈走回来这样一定可以保证到达每个点的时间必定不小于绕若干圈到每个点的时间,且总时间一致。

因此,在环上的这个问题本质上和序列上唯一不同的地方在于:可以从不同的点出发。

那么按照套路可以拆环成链,于是可以枚举每个起点 (i),问题就变成了上面哪个序列问题了,即求:

[minlimits_{i = 1} ^ n {maxlimits_{j = i} ^ {i + n - 1} A_j + i + n - 1} ]

首先 (n - 1) 与相对大小无关,提出去:

[minlimits_{i = 1} ^ n {maxlimits_{j = i} ^ {i + n - 1} A_j + i} + n - 1 ]

于此同时,基于观察我们发现 (forall j, j > i + n - 1, A_j < A_{j - n}) 这是显然的。

于是可以考虑将上式的范围扩大一下,变成后缀的 (max),这样看起来更有希望推导下去:

[minlimits_{i = 1} ^ n {maxlimits_{j = i} ^ {2n} A_j + i} + n - 1 ]

再次基于观察可以发现,这个后缀 (max) 一定是一个单调递减的序列。

于是可以令从后往前出现 (max) 变化的点的位置依次为 (p_k, p_{k - 1}, cdots p_1)

则可以发现 (f_{i} = A_{p_{q + 1}} + i = T_{p_{q + 1}} - p_{q + 1} + i(p_q < i < p_{q + 1}))

所以 (p_q < i < p_{q + 1})(f_i) 单调递增

同时又有 (A_{p_q} > A_{p_{q + 1}} Rightarrow T_{p_q} - p_q > T_{p_{q + 1}} - p_{q + 1})

可知 (f_{p_q} = A_{p_q} + p_q = T_{p_q} ge T_{p_{q + 1}} + p_q - p_{q + 1} + 1 = f_{p_q + 1})

因此,全局的最小值只可能在每个 (f_{p_i + 1}(1 le i < k)) 处取到。

于是我们只需要维护出 (p_i) 的位置就好了,这让我们想起了 楼房重建 这题,同样是维护了一个上升的单调栈。

那么在这里我们同样可以借鉴这道题的思路,每次合并两个区间时二分答案对接。

不同的是,本题的单调栈是从后往前的,因此需要在左区间上二分对接。

基于观察可知,当当前对接区间的右区间最大值已经大于需要对接的高度时,左边的所有位置均是可取的,因此我们需要维护每个区间的单调栈内左区间的答案。

否则,直接往右区间递归即可。

最终停下来的哪个点就会是最靠左的,大于等于对接高度的点。

如果两者相等,那么可以在这个位置取到答案,否则只能在后面一个位置取到答案。

可以发现,上面这个过程刚好计算出了左区间的答案,最终的答案就是就是根节点左区间的答案(因为整个区间为 ([1, 2n]))。

于是我们就在 (O((n + m) log ^ 2 n)) 的时间复杂度内解决了这个问题。

#include <bits/stdc++.h>
using namespace std;
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r >> 1)
#define rep(i, l, r) for (int i = l; i <= r; ++i)
const int N = 2e5 + 5;
struct tree { int max, min;} t[N << 2];
int n, m, x, y, ans, type, a[N];
int read() {
    char c; int x = 0, f = 1;
    c = getchar();
    while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
    while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
    return x * f;
}
int find(int p, int l, int r, int k) {
    if(l == r) return (t[p].max == k ? k + l : k + l + 1);
    if(t[rs].max > k) return min(t[p].min, find(rs, mid + 1, r, k));
    else return find(ls, l, mid, k);
}
void up(int p, int l, int r) {
    t[p].max = max(t[ls].max, t[rs].max);
    t[p].min = find(ls, l, mid, t[rs].max);
}
void build(int p, int l, int r) {
    if(l == r) { t[p].min = a[l] + l, t[p].max = a[l]; return;}
    build(ls, l, mid), build(rs, mid + 1, r);
    up(p, l, r);
}
void update(int p, int l, int r, int x, int y, int k) {
    if(l >= x && r <= y) { t[p].min = k + l, t[p].max = k; return;}
    if(mid >= x) update(ls, l, mid, x, y, k);
    if(mid < y) update(rs, mid + 1, r, x, y, k);
    up(p, l, r);
}
int main() {
    n = read(), m = read(), type = read();
    rep(i, 1, n) a[i] = read() - i, a[i + n] = a[i] - n;
    build(1, 1, 2 * n), ans = t[1].min + n - 1;
    printf("%d
", ans);
    rep(i, 1, m) {
        x = read(), y = read();
        if(type) x = (x ^ ans), y = (y ^ ans);
        update(1, 1, 2 * n, x, x, y - x), update(1, 1, 2 * n, x + n, x + n, y - x - n);
        printf("%d
", ans = t[1].min + n - 1);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Go7338395/p/13855439.html