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