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