题目概述
题目描述
在 2016 年,佳媛姐姐刚刚学习了树,非常开心。
现在他想解决这样一个问题:给定一颗有根树,根为 \(1\) ,有以下两种操作:
标记操作:对某个结点打上标记。(在最开始,只有结点 \(1\) 有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)
询问操作:询问某个结点最近的一个打了标记的祖先。(这个结点本身也算自己的祖先)
你能帮帮她吗?
输入输出格式
输入格式
第一行两个正整数 \(N\) 和 \(Q\) 分别表示节点个数和操作次数。
接下来 \(N-1\) 行,每行两个正整数 \(u,v \,\, (1 \leqslant u,v \leqslant n)\) 表示 \(u\) 到 \(v\) 有一条有向边。
接下来 \(Q\) 行,形如 oper num
,oper
为 C
时表示这是一个标记操作, oper
为 Q
时表示这是一个询问操作。
输出格式
输出一个正整数,表示结果
输入输出样例
输入样例 #1
5 5 1 2 1 3 2 4 2 5 Q 2 C 2 Q 2 Q 5 Q 3
输出样例 #1
1 2 2 1
数据范围
\(30\%\) 的数据,\(1 \leqslant N, Q \leqslant 1000\) ; \(70\%\) 的数据,\(1 \leqslant N, Q \leqslant 10000\) ; \(100\%\) 的数据,\(1 \leqslant N, Q \leqslant 100000\) 。
解题报告
题意理解
- 刚开始根节点有标记
- 找到一个点祖辈上面离他最近的标记
- 有修改操作,每次给一个点打标记
算法解析
只要有修改操作,而且是树上快速查询,我们总能够想到代码量著名的树链剖分算法。
这道题目,线段树需要支持,区间最值操作
为什么是区间最值啊?
因为我们知道,对于一个点上面的点,肯定是深度越深,离他越近。
因此我们不妨令线段树,存储一个节点的深度。或者说是,DFS序
因为对于一条链而言,越深的点,他们的DFS序列将会是越大的,
那么我们就可以查询区间最大值,找到离这个点最近的祖先标记,于是一切都迎刃而解了。
这就是一个树链剖分的模板题目了,不过我们这里使用的代码是zkw线段树,也算是一种新型尝试了。
而且我们将两个搜索,融合在一起,成为一个搜索呢。
#include <bits/stdc++.h> using namespace std; const int N=100200,P=1<<17; vector<int> g[N]; int dfn[N],pre[N],size[N],cnt,x,y,p,n,q,sum[P<<1]; void dfs(int x) { size[x]=1; dfn[x]=++cnt; pre[cnt]=x; for(int y:g[x]) { if(!size[y]) { dfs(y); size[x]+=size[y]; } } } inline void Update(int l, int r, int x)//zkw线段树可还行 { for(l+=P-1, r+=P+1; l^r^1; l>>=1, r>>=1) { if(~l&1) sum[l^1]=max(sum[l^1],x); if(r&1) sum[r^1]=max(sum[r^1],x); } } inline int Query(int p) { int ans=0; for(p+=P; p; p>>=1) ans=max(ans,sum[p]); return ans; } int main() { scanf("%d%d",&n,&q); for(int i=1; i<n; i++) { scanf("%d%d",&x,&y); g[x].push_back(y); } dfs(1); sum[1]=1; for(int i=1; i<=q; i++) { char opt[10]; scanf("%s%d",&opt,&x); if(opt[0]=='C') Update(dfn[x],dfn[x]+size[x]-1,dfn[x]); else printf("%d\n",pre[Query(dfn[x])]); } return 0; }