GMOJ 6943. 【2020.01.05冬令营模拟】社会实践(practice)

GMOJ6943. 社会实践(practice)

\(Description\)

给定一个长度为 \(n\) 仅由 \(\text{{1, 2, 3}}\) 构成的序列 \(s\)\(s_i\) 表示若由盘子 \(i\) 构成询问,那么 \(i\) 会在柱子 \(s_i\) 上,盘 \(1 \text{ ~ } n\) 大小单调递增,且每根柱上的盘大小递增。及 \(m\) 个操作:

  • 修改操作:给定 \(x, \ v\),将 \(s_x\) 修改为 \(p\)
  • 询问操作:给定 \(l, \ r\),表示询问由区间 \([l, r]\) 构成,求将所有盘子移到一根柱上的 最少移动步数

\(Data \ Constraint\)

\(1 \leq n, \ m \leq 3 \times 10^5\)

\(Solution\)

考虑询问的逆向操作:一开始盘 \(l \text{ ~ } r\) 都在同一根柱上,求移回相应柱子的最少移动步数。

那么有一个明显的贪心:将一摞盘子放在盘 \(r\) 对应的柱上,从大到小一次复位。

考虑一次操作,若盘 \(i\) 不在相应的柱上,那么我们需花费 \(2^{i - l} - 1\) 步将盘 \(l \text{ ~ } i - 1\) 移到除当前柱和目标柱的另一根柱上,然后花费 \(1\) 步复位 \(i\),共花费 \(2^{i - l}\) 步。

经过这次操作,复位了 \(i \text{ ~ } r\),盘 \(l \text{ ~ } i - 1\) 整体没变并且被移到了另一根柱子上,这是一个子问题。

那么若不固定起始柱的话,对于还原一个区间 \([l, r]\),可以由三个量表示:起始柱,花费步数,终止柱(即复位 \(l\) 后若还有盘的话会放在哪一根柱子) ,并且如果我们知道了 起始柱花费步数终止柱 就是唯一确定的。

所以我们便可以用线段树维护,对于每个区间存下起始柱及其对应的花费步数和终止柱,合并也很简单,直接枚举右区间的起始柱,那么其终止柱就是复位到左区间时的起始柱,总花费就为 左区间花费 + 右区间花费 \(\times 2^{mid - l + 1}\)

\(Code\)

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define N 300000
#define mo 998244353

#define fo(i, x, y) for(int i = x; i <= y; i ++)

void read(int &x) {
    char ch = getchar(); x = 0;
    while (ch < '0' || ch > '9') ch = getchar();
    while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + ch - 48, ch = getchar();
}

int a[N + 1], mi[N + 1];

int n, m;

namespace Tree { 
    #define lson (t << 1)
    #define rson (t << 1 | 1)
    int ls[N << 2][3], cs[N << 2][3];
    void Pushu(int t, int l, int r, int mid) {
        fo(i, 0, 2) {
            ls[t][i] = ls[lson][ls[rson][i]];
            cs[t][i] = (cs[lson][ls[rson][i]] + 1ll * cs[rson][i] * mi[mid - l + 1] % mo) % mo;
        }
    }
    void Build(int t, int l, int r) {
        if (l == r) {
            fo(i, 0, 2)
                cs[t][i] = a[l] == i ? (ls[t][i] = i, 0) : (ls[t][i] = (3 ^ a[l] ^ i), 1);
            return;
        }
        int mid = l + r >> 1;
        Build(lson, l, mid), Build(rson, mid + 1, r);
        Pushu(t, l, r, mid);
    }
    void Chang(int t, int l, int r, int k) {
        if (l == r) {
            fo(i, 0, 2)
                cs[t][i] = a[l] == i ? (ls[t][i] = i, 0) : (ls[t][i] = (3 ^ a[l] ^ i), 1);
            return;
        }
        int mid = l + r >> 1;
        k <= mid ? Chang(lson, l, mid, k) : Chang(rson, mid + 1, r, k);
        Pushu(t, l, r, mid);
    }
    int now = 0, ans = 0;
    void Calc(int t, int l, int r, int x, int y) {
        if (x <= l && r <= y) {
            (ans += 1ll * cs[t][now] * mi[l - x] % mo) %= mo, now = ls[t][now];
            return;
        }
        int mid = l + r >> 1;
        if (y > mid) Calc(rson, mid + 1, r, x, y);
        if (x <= mid) Calc(lson, l, mid, x, y);
    }
    #undef lson
    #undef rson
}

using namespace Tree;

int main() {
    freopen("practice.in", "r", stdin);
    freopen("practice.out", "w", stdout);

    read(n), read(m);
    fo(i, 1, n) read(a[i]), -- a[i];

    mi[0] = 1;
    fo(i, 1, n) mi[i] = 1ll * mi[i - 1] * 2 % mo;
    Build(1, 1, n);

    int opt, x, y;
    fo(Case, 1, m) {
        read(opt), read(x), read(y);
        if (opt == 1) {
            if (a[x] == -- y) continue;
            a[x] = y;
            Chang(1, 1, n, x);
        } else {
            now = a[y], ans = 0;
            Calc(1, 1, n, x, y);
            printf("%d\n", ans);
        }
    }

    return 0;
}
posted @ 2021-01-10 20:24  buzzhou  阅读(55)  评论(0编辑  收藏  举报