【CF1017G】The Tree(线段树)
- 给定一棵 \(n\) 个点的以 \(1\) 为根的树,初始所有点为白色。
- \(q\) 次操作,分为三种:若 \(x\) 为白色则将其染黑,否则递归对 \(x\) 的所有子节点进行该操作;将 \(x\) 的子树所有点染白;询问 \(x\) 的颜色。
- \(2\le n\le10^5\),\(1\le q\le10^5\)
线段树
挺久以前看到过这道题的,当时胡了一个树链剖分的做法,在每个点上考虑它的祖先们的修改对它的影响,好像要维护一个后缀最小值什么的,不知道出于什么原因当时没有写。
今天看到 \(\color{black}{2}\color{red}{75307894a}\) 在嘲讽怎么没人写一个 \(\log\) 的做法,才发现这题原来还可以直接用线段树做。
核心思想在于考虑维护 \(F(x)\) 表示 \(x\) 到根上的白点个数。
第一个操作就相当于将 \(x\) 子树内到 \(x\) 路径上存在白点的节点的 \(F\) 值都减 \(1\),等价于将 \(x\) 子树内全部 \(F\) 值减 \(1\) 然后向 \(F(fa_x)\) 取 \(\max\)。
第二个操作就相当于将 \(x\) 子树内每个点 \(i\) 的 \(F\) 值修改为 \(F(fa_x)+Dep_i-Dep_{fa_x}\),也就是 \((F(fa_x)-Dep_{fa_x})+Dep_i\),只需打上一个标记 \(F(fa_x)-Dep_{fa_x}\) 即可。
第三个操作只要询问出 \(F(fa_x)\) 和 \(F(x)\),若 \(F(fa_x)\not=F(x)\) 说明 \(x\) 为白点,否则 \(x\) 为黑点。
整理一下,我们需要实现区间加、区间取 \(\max\)、区间打标记赋值、单点询问,可以按照赋值、加法、取 \(\max\) 的顺序维护标记。
代码:\(O(n\log n)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define INF (int)1e9
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
using namespace std;
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
char oc,FI[FS],*FA=FI,*FB=FI;
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
}using namespace FastIO;
int n,d,fc[N+5],dI[N+5],dO[N+5],D[N+5],f[N+5],ee,lnk[N+5];struct edge {int to,nxt;}e[N+5];
void dfs(CI x) {fc[dI[x]=++d]=x;for(RI i=lnk[x],y;i;i=e[i].nxt) (y=e[i].to)^f[x]&&(D[y]=D[x]+1,dfs(y),0);dO[x]=d;}//dfs预处理
class SegmentTree//线段树
{
private:
#define PT RI l=1,RI r=n,RI o=1
#define LT l,u,o<<1
#define RT u+1,r,o<<1|1
#define TF(o,v) (F[o]+=v,G[o]^INF&&(G[o]+=v))//加法,并修改取max的标记
#define TG(o,v) (G[o]=G[o]^INF?max(G[o],v):v)//取max
#define TP(o,v) (F[o]=0,G[o]=INF,P[o]=v)//赋值,清空其他标记
#define PD(o) (P[o]^INF&&(TP(o<<1,P[o]),TP(o<<1|1,P[o]),P[o]=INF),\
F[o]&&(TF(o<<1,F[o]),TF(o<<1|1,F[o]),F[o]=0),G[o]^INF&&(TG(o<<1,G[o]),TG(o<<1|1,G[o]),G[o]=INF))//按照赋值、加法、取max的顺序下传
int F[N<<2],G[N<<2],P[N<<2];
public:
void UF(CI L,CI R,PT) {if(L<=l&&r<=R) return (void)TF(o,-1);RI u=l+r>>1;PD(o),L<=u&&(UF(L,R,LT),0),R>u&&(UF(L,R,RT),0);}//区间减1
void UG(CI L,CI R,CI v,PT) {if(L<=l&&r<=R) return (void)TG(o,v);RI u=l+r>>1;PD(o),L<=u&&(UG(L,R,v,LT),0),R>u&&(UG(L,R,v,RT),0);}//区间取max
void UP(CI L,CI R,CI v,PT) {if(L<=l&&r<=R) return (void)TP(o,v);RI u=l+r>>1;PD(o),L<=u&&(UP(L,R,v,LT),0),R>u&&(UP(L,R,v,RT),0);}//区间赋值
int Q(CI x,PT) {if(P[o]^INF) return max(D[fc[x]]+P[o]+F[o],G[o]^INF?G[o]:0);RI u=l+r>>1;PD(o);return x<=u?Q(x,LT):Q(x,RT);}//单点询问
}S;
int main()
{
RI Qt,i,x,y;for(read(n,Qt),i=2;i<=n;++i) read(f[i]),add(f[i],i);D[1]=1,dfs(1);
RI g;W(Qt--) switch(read(y,x),g=x^1?S.Q(dI[f[x]]):0,y)//g存下f[x]的F值,无论哪种操作都会用到
{
case 1:S.UF(dI[x],dO[x]),S.UG(dI[x],dO[x],g);break;//区间减1,然后向F(f[x])取max
case 2:S.UP(dI[x],dO[x],g-D[f[x]]);break;//区间打标记赋值为F(f[x])-D[f[x]]+D[i]
case 3:puts(S.Q(dI[x])^g?"white":"black");break;//将F(x)与F(f[x])比较
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒