Sooke
2019-04-02 07:58:03
先来扯些真实的废话。
这八成是考场上最可做的题,原因有以下:
众所周知,当九条遇上诸如斗地主、麻将、五子棋等的打完暴力直接跳过。(甚至打不出暴力)
经回忆,往年省选最简单的题中往往都有线段树。
大多数毒瘤场的开题顺序都是 2、3、1。
下面,我将以我场上的心路历程,来进行本题的讲解。
既然每次有
void modify(int u, int l, int r, int pl, int pr) {
if (l == pl && r == pr) {
pushTag(u);
return;
}
int mid = l + r >> 1;
pushDown(u);
if (pr <= mid) {
modify(u << 1, l, mid, pl, pr);
} else if (pl > mid) {
modify(u << 1 | 1, mid + 1, r, pl, pr);
} else {
modify(u << 1, l, mid, pl, mid);
modify(u << 1 | 1, mid + 1, r, mid + 1, pr);
}
}
尽管线段树的结点很多,实际上,根据它们的性质分类,无非也就那么五种(认真分辨,这很重要!):
一类点(白色):在
\mathrm{modify} 操作中,被半覆盖的点。二类点(深灰):在
\mathrm{modify} 操作中,被全覆盖的点,并且能被遍历到。三类点(橙色):在
\mathrm{modify} 操作中,未被覆盖的点,并且可以得到\mathrm{pushdown} 来的标记。四类点(浅灰):在
\mathrm{modify} 操作中,被全覆盖的点,并且不能被遍历到。五类点(黄色):在
\mathrm{modify} 操作中,未被覆盖的点,并且不可能得到\mathrm{pushdown} 来的标记。
在代码中,五种点分别是这样出现的:
void modify(int u, int l, int r, int pl, int pr) {
if (l == pl && r == pr) {
pushTag(u); // 给二类点打标记。
// 之后的 pushdown,可能会把标记带到二类点以下的四类点去。
return;
}
int mid = l + r >> 1;
pushDown(u); // 这里是一类点,会进行 pushdown 操作。
if (pr <= mid) {
modify(u << 1, l, mid, pl, pr);
// u << 1 | 1 一边就是三类点,上面的 pushdown 会传到这里。
// 之后的 pushdown,可能会把标记带到三类点以下的五类点去。
} else if (pl > mid) {
modify(u << 1 | 1, mid + 1, r, pl, pr);
// u << 1 一边就是三类点,上面的 pushdown 会传到这里。
// 之后的 pushdown,可能会把标记带到三类点以下的五类点去。
} else {
modify(u << 1, l, mid, pl, mid);
modify(u << 1 | 1, mid + 1, r, mid + 1, pr);
}
}
类别是分得蛮清楚了,但这又有什么卵用呢?注意到同类点性质相同,倘若给它们记录
没错!由于转移视类别而定,只有五种,我们可以考虑只用一棵线段树,然后在线段树上
设
f_{i,\ u} 为第i 次\mathrm{modify} 后,u 号点在2^i 棵线段树中,多少棵被打标记。
看上去非常可做!赶紧试试五种点下的转移分别是什么?
显然只在一类点
只在二类点上直接打标记,所以只要
不
哈?为什么要用
当然没有!办法有得是!我们不妨将需要的那个状态定义出来:
设
g_{i,\,u} 为第i 次\mathrm{modify} 后,u 号点在2^i 棵线段树中,多少棵满足1\to u 上没有任何标记。反过来,满足
1\to u 上至少有一个标记,就是2^{i} - g_{i,\,u} 。
这次准备挺充分了!再试试?
同样的道理,只要
只要
这是刚才的难点,现在有备无患了。必须
不管是四类点还是五类点,都算是
四类点和五类点的
这总不用说了吧。
这一遍下来,转移好像没啥问题了。最后是怎么实现。
每次修改暴力转移每个点绝对是不可能的。发现每次一类点、二类点、三类点只有
除此之外,维护
具体实现中可以通过把“方案数”转成“概率”从而减少一半的懒标记和常数,本人较懒,就没写了。
常见错误:
空间开小,以写法不同,四倍空间不一定足够,建议在空间方面任性一点。
转移顺序不要错,先
f 后g 。
#include <cstdio>
inline int read() {
char c = getchar(); int x = 0;
while (c < '0' || c > '9') { c = getchar(); }
while (c >= '0' && c <= '9') { x = (x << 1) + (x << 3) + (c & 15); c = getchar(); }
return x;
}
const int N = 2000005, p = 998244353;
inline int add(int x, int y) { x += y; return x >= p ? x - p : x; }
inline int sub(int x, int y) { x -= y; return x >= 0 ? x : x + p; }
int n, m, k = 1;
struct SegmentTree {
int f[N], g[N], tf[N], tg[N], sf[N];
inline void pushUp(int u) { sf[u] = add(f[u], add(sf[u << 1], sf[u << 1 | 1])); }
inline void pushTf(int u, int x) { f[u] = 1ll * f[u] * x % p; tf[u] = 1ll * tf[u] * x % p; sf[u] = 1ll * sf[u] * x % p; }
inline void pushTg(int u, int x) { g[u] = 1ll * g[u] * x % p; tg[u] = 1ll * tg[u] * x % p; }
inline void pushDown(int u) {
if (tf[u] != 1) { pushTf(u << 1, tf[u]); pushTf(u << 1 | 1, tf[u]); tf[u] = 1; }
if (tg[u] != 1) { pushTg(u << 1, tg[u]); pushTg(u << 1 | 1, tg[u]); tg[u] = 1; }
}
void build(int u, int l, int r) {
g[u] = tf[u] = tg[u] = 1; // 边界。
if (l == r) { return; } int mid = l + r >> 1;
build(u << 1, l, mid); build(u << 1 | 1, mid + 1, r);
}
void modify(int u, int l, int r, int pl, int pr) {
pushDown(u);
if (l == pl && r == pr) {
f[u] = add(f[u], k); // 二类点。
pushTf(u << 1, 2); pushTf(u << 1 | 1, 2); // 四类点。
} else {
int mid = l + r >> 1, ul = u << 1, ur = ul | 1;
g[u] = add(g[u], k); // 一类点。
if (pr <= mid) {
modify(ul, l, mid, pl, pr); pushDown(ur);
f[ur] = add(f[ur], sub(k, g[ur])); g[ur] = add(g[ur], g[ur]); // 三类点。
pushTf(ur << 1, 2); pushTf(ur << 1 | 1, 2);
pushTg(ur << 1, 2); pushTg(ur << 1 | 1, 2); // 五类点。
pushUp(ur);
} else if (pl > mid) {
modify(ur, mid + 1, r, pl, pr); pushDown(ul);
f[ul] = add(f[ul], sub(k, g[ul])); g[ul] = add(g[ul], g[ul]); // 三类点。
pushTf(ul << 1, 2); pushTf(ul << 1 | 1, 2);
pushTg(ul << 1, 2); pushTg(ul << 1 | 1, 2); // 五类点。
pushUp(ul);
} else {
modify(ul, l, mid, pl, mid); modify(ur, mid + 1, r, mid + 1, pr);
}
}
pushUp(u);
}
} smt;
int main() {
n = read(); m = read(); smt.build(1, 1, n);
for (int opt, l, r; m; m--) {
opt = read();
if (opt == 1) {
l = read(); r = read();
smt.modify(1, 1, n, l, r); k = add(k, k);
} else { printf("%d\n", smt.sf[1]); }
}
return 0;
}
这题的给点分类的思路算是比较妙的了,不少人设出了