[HEOI2016/TJOI2016]树
更好的阅读体验
题目概述
题目描述
在 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;
}
这题只算是 dfs序 + 线段树小结合吧,不算树链剖分吧 #小声
大佬所言极是