【前置知识】
树链剖分(知识点太多了这里就不详细讲原理了!)
线段树 (知识点太多了这里就不详细讲原理了!)
这里就讲讲方法
【难度】
3.5
/
10
3.5/10
3.5/10
模板题
【题意】
给你一个有根树,根节点为
1
1
1。
顶点编号从
1
1
1 到
n
n
n ,初始每个顶点都代表一个空的水库。
你有一下三种操作:
(1)给水库
v
v
v 充水。这样,
v
v
v 和它所有的儿子的水库都会充满水。
(2)给水库
v
v
v 放水。这样,
v
v
v 和它所有的祖先的水库都会变空。
(3)询问 水库
v
v
v 现在是充满水的还是放空的。
【数据范围】
顶点个数:
1
≤
n
≤
5
×
1
0
5
1\le n\le 5\times10^5
1≤n≤5×105
查询次数:
1
≤
q
≤
5
×
1
0
5
1\le q\le 5\times10^5
1≤q≤5×105
保证给的是树。
【样例输入】
n
n
n 顶点数
n
−
1
n-1
n−1条边
q
q
q 查询次数
o
p
e
r
a
t
i
o
n
i
d
operation\quad id
operationid
5
1 2
5 1
2 3
4 2
12
1 1
2 3
3 1
3 2
3 3
3 4
1 2
2 4
3 1
3 3
3 4
3 5
【样例输出】
0
0
0
1
0
1
0
1
【思路】
【操作1的做法】
对于某个顶点
v
v
v ,我们易得该顶点的
d
f
s
序
\color{cyan}dfs序
dfs序:
d
f
n
[
v
]
\pmb{dfn[v]}
dfn[v]dfn[v]dfn[v] 与
子
树
大
小
\color{cyan}子树大小
子树大小 :
S
i
z
[
v
]
\pmb{Siz[v]}
Siz[v]Siz[v]Siz[v]。
对
于
某
棵
d
f
s
树
的
顶
点
v
,
它
及
它
子
树
的
d
f
s
序
是
连
续
的
区
间
管
理
范
围
为
[
d
f
n
[
v
]
,
d
f
n
[
v
]
+
S
i
z
[
v
]
−
1
]
\color{red}对于某棵dfs树的顶点v,它及它子树的dfs序是连续的\\ 区间管理范围为 [dfn[v],dfn[v]+Siz[v]-1]
对于某棵dfs树的顶点v,它及它子树的dfs序是连续的区间管理范围为[dfn[v],dfn[v]+Siz[v]−1]
我们利用这个性质,加上
线
段
树
\color{green}线段树
线段树的遍历的区间加 / 区间求和 等操作进行处理。
【操作3的做法】
有了线段树,单点查询不是易如反掌?
【操作2的做法】
关键我们需要知道怎么修改该顶点到根节点的所有顶点置零操作。
对啦!就是通过
树
链
剖
分
\color{red}树链剖分
树链剖分 来快速到达根节点!(喂只有你才是这么写的吧。。)
我们分好轻重链后,可以快速 上 跳 \color{green}上跳 上跳 操作,来在 O ( log N ) \color{red}O(\log N) O(logN) 的时间内到达根节点而不是暴力做法的 O ( N ) O(N) O(N)
然后对于每一段上跳的区间,我们进行线段树置零操作,所以时间复杂度为 O ( log log N ) \color{red}O(\log\log N) O(loglogN)
区间置零等于区间乘以
0
0
0呀!!
区间置
1
1
1 等于区间乘以
0
0
0 再加上
1
1
1 呀!!
使用有乘法和加法操作的线段树板子即可。(甚至可以不用乘法,不过看着不爽!)
【核心代码】
时间复杂度: O ( N + Q × N log log N ) O(N+Q\times N\log\log N) O(N+Q×NloglogN)
/*
_ __ __ _ _
| | \ \ / / | | (_)
| |__ _ _ \ V /__ _ _ __ | | ___ _
| '_ \| | | | \ // _` | '_ \| | / _ \ |
| |_) | |_| | | | (_| | | | | |___| __/ |
|_.__/ \__, | \_/\__,_|_| |_\_____/\___|_|
__/ |
|___/
*/
const int MAX = 6e5+50;
const ll MOD = 998244353;
int Top[MAX],Siz[MAX],Dep[MAX],Fa[MAX],Son[MAX],dfn[MAX],nfd[MAX];
int a[MAX];
int tim;
int n,q;
vector<int>G[MAX]; /// 难道没人喜欢可爱的邻接表吗 o(' 3 `)o
/// 线段树板子(太大了,我都卡了。)
#define mo MOD
struct tree
{
int l,r;//代表节点维护的区间范围;
ll data; //代表该节点维护的值;
ll lazy_Add; //涉及加法lazy标记的东西;
ll lazy_Mul; //涉及乘法lazy标记的东西;
}t[MAX << 2];
inline int lson(int p){return p << 1;}//左儿子;
inline int rson(int p){return p << 1 | 1;}//右儿子;
void build(int p,int l,int r)
{
t[p].l = l,t[p].r = r,t[p].lazy_Mul = 1;//以p为编号的节点维护的区间为[l,r];
if(l == r)//叶子节点存放真实的数值;
{
t[p].data = a[l] % mo;
return;
}
int mid = t[p].l + t[p].r >> 1;
build(lson(p),l,mid);
build(rson(p),mid + 1,r);
//回溯时将其子节点的信息存下来;
t[p].data = (t[lson(p)].data + t[rson(p)].data) % mo;
}
void push_down(int p)
{
t[lson(p)].data = (t[lson(p)].data * t[p].lazy_Mul % mo + (t[p].lazy_Add * (t[lson(p)].r - t[lson(p)].l + 1) % mo)) % mo;
t[rson(p)].data = (t[rson(p)].data * t[p].lazy_Mul % mo + (t[p].lazy_Add * (t[rson(p)].r - t[rson(p)].l + 1) % mo)) % mo;
t[lson(p)].lazy_Mul = t[lson(p)].lazy_Mul * t[p].lazy_Mul % mo;
t[lson(p)].lazy_Add = (t[lson(p)].lazy_Add * t[p].lazy_Mul % mo + t[p].lazy_Add) % mo;
t[rson(p)].lazy_Mul = t[rson(p)].lazy_Mul * t[p].lazy_Mul % mo;
t[rson(p)].lazy_Add = (t[rson(p)].lazy_Add * t[p].lazy_Mul % mo + t[p].lazy_Add) % mo;
t[p].lazy_Mul = 1;
t[p].lazy_Add = 0;
}
void update_Add(int p,int l,int r,ll value)
{
if(l <= t[p].l && r >= t[p].r)//区间被覆盖,就修改;
{
t[p].data = (t[p].data + value * (t[p].r - t[p].l + 1) % mo ) % mo;
t[p].lazy_Add = (t[p].lazy_Add + value) % mo;
return;
}
push_down(p);//如果没有被覆盖,那就需要继续向下找;
//考虑儿子所维护的区间可能因为懒标记的存在而没有修改,因此将懒标记下放;
int mid = t[p].l + t[p].r >> 1;
if(l <= mid)update_Add(lson(p),l,r,value);//覆盖了左儿子就修改左儿子;
if(r > mid)update_Add(rson(p),l,r,value);//覆盖了右儿子就修改右儿子;
t[p].data = (t[lson(p)].data + t[rson(p)].data) % mo;
}
void update_Mul(int p,int l,int r,ll value)
{
if(l <= t[p].l && r >= t[p].r)
{
t[p].data = t[p].data * value % mo;
t[p].lazy_Mul = t[p].lazy_Mul * value % mo;
t[p].lazy_Add = t[p].lazy_Add * value % mo;
return;
}
push_down(p);
int mid = t[p].l + t[p].r >> 1;
if(l <= mid)update_Mul(lson(p),l,r,value);
if(r > mid)update_Mul(rson(p),l,r,value);
t[p].data = (t[lson(p)].data + t[rson(p)].data) % mo;
}
ll querySum(int p,int l,int r)
{
if(l <= t[p].l && r >= t[p].r)return t[p].data % mo;//覆盖了该区间
push_down(p);
ll sum = 0;
int mid = t[p].l + t[p].r >> 1;
if(l <= mid)sum = (sum + querySum(lson(p),l,r)) % mo;
if(r > mid)sum = (sum + querySum(rson(p),l,r)) % mo;
return sum % mo;//累加答案返回左右儿子的和;
}
void updatePa(int u,int v,int x){ /// 节点 u 到节点 v 之前都置零
while(Top[u] != Top[v]){
if(Dep[Top[u]] < Dep[Top[v]])swap(u,v);
update_Mul(1,dfn[Top[u]],dfn[u],x);
u = Fa[Top[u]];
}
if(Dep[u] > Dep[v])swap(u,v);
update_Mul(1 ,dfn[u] ,dfn[v] ,x);
}
void updateTr(int u){ /// 节点 u的子树都置 1
update_Mul(1 ,dfn[u] ,dfn[u] + Siz[u] - 1,0);
update_Add(1 ,dfn[u] ,dfn[u] + Siz[u] - 1,1);
}
void Dfs_Init(int u,int fa,int dep){ /// 第一次 dfs 求出节点深度 重儿子 子树大小
Dep[u] = dep;
Siz[u] = 1;
Fa[u] = fa;
for(auto it : G[u]){
if(it == fa)continue;
Dfs_Init(it ,u ,dep + 1);
if((!Son[u]) || Siz[Son[u]] < Siz[it])Son[u] = it;
Siz[u] += Siz[it];
}
}
void Split(int u,int top){ /// 第二次 dfs 求出 dfn
Top[u] = top;
dfn[u] = ++tim;
nfd[tim] = u;
if(!Son[u])return ;
Split(Son[u] ,top);
for(auto it : G[u]){
if(it != Fa[u] && it != Son[u])
Split(it ,it);
}
}
void Get(int n,int root){ /// 初始化,建图建树,两次dfs接口
for(int i=0;i<=n+5;++i)dfn[i] = nfd[i] = Son[i] = Siz[i] = a[i] = 0,G[i].clear();
for(int i=1;i<n;++i){
int ta,tb;
ta = read();
tb = read();
G[ta].push_back(tb);
G[tb].push_back(ta);
}
build(1 ,1 ,n);
tim = 0;
Dfs_Init(root ,-1 ,1);
Split(root ,root);
}
int main()
{
n = read();
Get(n,1);
q = read();
for(int i=1;i<=q;++i){
int op,ta;
op = read();
ta = read();
if(op == 1)updateTr(ta); /// 充水
else if(op == 2)updatePa(ta ,1 ,0); /// 放水
else{
printf("%d\n",querySum(1,dfn[ta],dfn[ta])); /// 单点查询
}
}
return 0;
}