[HEOI2016/TJOI2016]树

题目概述

题目描述

在 2016 年,佳媛姐姐刚刚学习了树,非常开心。

现在他想解决这样一个问题:给定一颗有根树,根为 \(1\) ,有以下两种操作:

  1. 标记操作:对某个结点打上标记。(在最开始,只有结点 \(1\) 有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)

  2. 询问操作:询问某个结点最近的一个打了标记的祖先。(这个结点本身也算自己的祖先)

    你能帮帮她吗?

输入输出格式

输入格式

第一行两个正整数 \(N\)\(Q\) 分别表示节点个数和操作次数。

接下来 \(N-1\) 行,每行两个正整数 \(u,v \,\, (1 \leqslant u,v \leqslant n)\) 表示 \(u\)\(v\) 有一条有向边。

接下来 \(Q\) 行,形如 oper numoperC 时表示这是一个标记操作, operQ 时表示这是一个询问操作。

输出格式

输出一个正整数,表示结果

输入输出样例

输入样例 #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\)

解题报告

题意理解

  1. 刚开始根节点有标记
  2. 找到一个点祖辈上面离他最近的标记
  3. 有修改操作,每次给一个点打标记

算法解析

只要有修改操作,而且是树上快速查询,我们总能够想到代码量著名的树链剖分算法。

这道题目,线段树需要支持,区间最值操作

为什么是区间最值啊?

因为我们知道,对于一个点上面的点,肯定是深度越深,离他越近。

因此我们不妨令线段树,存储一个节点的深度。或者说是,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;
}
posted @ 2019-11-10 20:21  秦淮岸灯火阑珊  阅读(218)  评论(0编辑  收藏  举报