「有趣的链剖题」 [Codeforces 1017G] The Tree
皎月半洒花
2020-06-16 21:28:35
这是真神题。
首先观察发现,操作的复杂度向修改倾斜,即询问复杂度比较低,可以考虑询问的时候搞点事情。
考虑 $(1)$ 操作的本质。因为要向询问倾斜,所以考虑如果某个点 $z$ 会被 `1 x` 影响到 ,那么必然有 $x\sim z$ 这条路径上除了 $x$ 之外的所有点都已经被染黑了。
考虑忽略 $(2)$ 操作时,如何通过维护权值实现这个事情。一开始将全部的点权设为 $0$,每次 $(1)$ 操作就是对该点进行单点 $+1$ 。这样询问就可以考虑询问 $root\to x$ 这条路径上,是否存在一个后缀 $(s,x)$ 的权和等于 $(s,x)$ 的长度。这样直接做本质上是一个树链上的线段树二分,但其实可以用一个比较传统的技巧优化这个过程,即差分。考虑把每个点的初始点权设为 $-1$ ,这样就是询问是否存在一个后缀,其和 $\geqslant 0$ 。这个可以通过维护链上的最大后缀和来做,写起来简单一些。
然后考虑加上 $(2)$ 操作后如何快速维护。发现 $(2)$ 操作的本质是让整棵子树的点权均变为 $-1$ 。但这还不够,因为 $(2)$ 本质上是强制让某个点 $x$ 变成白色,而上面那个做法本质上是对修改离线,最后再分别确定每个修改对答案的贡献。不过也有比较简单的解决方式,即让 `2 x` 中的 $x$ 单点减一个权值 $v$ ,使得 $root\to x$ 上的最大后缀和也为 $-1$ 。不难发现 $v=qry(x)+1$ 。
于是最后就可以用 $n\log ^2n $ 的复杂度解决这个问题了。
```cpp
#include <cstdio>
#include <vector>
#include <cstring>
#define p_b push_back
using std :: max ;
using std :: min ;
using std :: vector ;
const int N = 300010 ;
int ans ;
int cnt ;
int n, q ;
int sz[N] ;
int fa[N] ;
int _id[N] ;
int dfn[N] ;
int son[N] ;
int dep[N] ;
int top[N] ;
vector <int> E[N] ;
void dfs1(int x, int da){
dep[x] = dep[da] + 1 ;
sz[x] = 1, fa[x] = da ;
for (auto y : E[x]){
if (y == da) continue ;
dfs1(y, x) ; sz[x] += sz[y] ;
if (sz[y] > sz[son[x]]) son[x] = y ;
}
}
void dfs2(int x, int tp){
top[x] = tp ; ++ cnt ;
_id[dfn[x] = cnt] = x ;
if (son[x])
dfs2(son[x], tp) ;
for (auto y : E[x])
if (y != fa[x] && y != son[x]) dfs2(y, y) ;
}
int seg[N * 3] ;
int val[N * 3] ;
int tag[N * 3] ;
#define lc (rt << 1)
#define rc (rt << 1 | 1)
void _up(int rt){
seg[rt] = seg[lc] + seg[rc] ;
val[rt] = max(val[rc], val[lc] + seg[rc]) ;
}
void _down(int rt, int l, int r){
if (!tag[rt]) return ;
int mid = (l + r) >> 1 ;
seg[rc] = - (r - mid) ;
seg[lc] = - (mid - l + 1) ;
val[lc] = -1, val[rc] = -1 ;
tag[rc] = tag[lc] = 1, tag[rt] = 0 ;
}
void build(int rt, int l, int r){
if (l == r)
return void(seg[rt] = val[rt] = -1) ;
int mid = (l + r) >> 1 ;
build(rc, mid + 1, r) ;
build(lc, l, mid) ; _up(rt) ;
}
void upd(int rt, int l, int r, int p, int v){
if (l == r){
seg[rt] += v ;
val[rt] += v ;
return void() ;
}
_down(rt, l, r) ;
int mid = (l + r) >> 1 ;
if (p <= mid) upd(lc, l, mid, p, v) ;
else upd(rc, mid + 1, r, p, v) ;
_up(rt) ;
}
void cov(int rt, int l, int r, int cl, int cr){
if (cl <= l && r <= cr){
seg[rt] = - (r - l + 1) ;
val[rt] = -1 ; return void(tag[rt] = 1) ;
}
int mid = (l + r) >> 1 ; _down(rt, l, r) ;
if (cr > mid) cov(rc, mid + 1, r, cl, cr) ;
if (cl <= mid) cov(lc, l, mid, cl, cr) ; _up(rt) ;
}
int sum(int rt, int l, int r, int ql, int qr){
if (ql <= l && r <= qr) return seg[rt] ;
int mid = (l + r) >> 1, ret = 0 ; _down(rt, l, r) ;
if (ql <= mid) ret += sum(lc, l, mid, ql, qr) ;
if (qr > mid) ret += sum(rc, mid + 1, r, ql, qr) ;
return ret ;
}
int qry(int rt, int l, int r, int ql, int qr){
_down(rt, l, r) ;
if (ql <= l && r <= qr) return val[rt] ;
int mid = (l + r) >> 1, ret = - n ; _down(rt, l, r) ;
if (ql <= mid) ret = max(ret, qry(lc, l, mid, ql, qr)) ;
if (qr > mid) ret = max(ret + sum(rc, mid + 1, r, ql, qr), qry(rc, mid + 1, r, ql, qr)) ;
return ret ;
}
int Q(int x){
int ret, v = 0 ;
ret = -(n + 1) ;
while (top[x] > 1){
ret = max(ret, v + qry(1, 1, n, dfn[top[x]], dfn[x])) ;
v += sum(1, 1, n, dfn[top[x]], dfn[x]) ; x = fa[top[x]] ;
}
// printf("%d\n", qry(1, 1, n, dfn[top[x]], dfn[x])) ;
ret = max(ret, v + qry(1, 1, n, 1, dfn[x])) ; return ret ;
}
int main(){
scanf("%d%d", &n, &q) ; int x, z ;
for (int y = 2 ; y <= n ; ++ y)
scanf("%d", &x), E[x].p_b(y), E[y].p_b(x) ;
dfs1(1, 0) ; dfs2(1, 1) ; build(1, 1, n) ;
// for (int i = 1 ; i <= n ; ++ i) printf("%d%c", top[i], " \n"[i == n]) ;
// printf("%d %d\n", i, sz[i]) ;
while (q --){
scanf("%d%d", &z, &x) ;
if (z == 1) upd(1, 1, n, dfn[x], 1) ;
else if (z == 2){
cov(1, 1, n, dfn[x], dfn[x] + sz[x] - 1) ;
upd(1, 1, n, dfn[x], - Q(x) - 1) ;
}
else if (z == 3){
// printf("%d\n", Q(x)) ;
ans = (bool)(Q(x) >= 0) ;
puts(ans ? "black" : "white") ;
}
}
return 0 ;
}
```