1. 程式人生 > 實用技巧 >GMOJ 6943. 【2020.01.05冬令營模擬】社會實踐(practice)

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;
}