博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
[洛谷P4092][HEOI2016/TJOI2016]树
阅读量:4926 次
发布时间:2019-06-11

本文共 2450 字,大约阅读时间需要 8 分钟。

Description

在2016年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树(根为1),有以下两种操作:

标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)
询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖先)
你能帮帮他吗?

Input

输入第一行两个正整数N和Q分别表示节点个数和操作次数

接下来N-1行,每行两个正整数u,v(1≤u,v≤n)表示u到v有一条有向边
接下来Q行,形如“opernum”oper为“C”时表示这是一个标记操作,oper为“Q”时表示这是一个询问操作对于每次询问操作。

Output

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

Sample Input

5 5

1 2
1 3
2 4
2 5
Q 2
C 2
Q 2
Q 5
Q 3

Sample Output

1

2
2
1

HINT

30%的数据,1 ≤ N, Q ≤ 1000

70%的数据,1 ≤ N, Q ≤ 10000
100%的数据,1 ≤ N, Q ≤ 100000


想法

这道题的做法其实挺多的……先说我自己的吧

先dfs一遍,记录dfs序
用线段树维护到结点最近的已标记祖先编号
每次标记操作,就更新它的子节点的最近已标记祖先编号(用lazy)
查询某个点就从根节点往下走,不断pushdown,更新下面结点的最近已标记祖先,一直走到要查询的那个点。
复杂度O(QlogN)

还有一种不错的做法。

把所有操作读入,要标记的节点先都标记上
dfs一遍。记录每个节点的它最近的已标记祖先(包括自己),并查集并在一起
从后往前处理每个操作。
若是标记操作,便把这个标记去掉,将它与父节点并在一起,表明它与它父节点的最近已标记祖先是同一个点。
若是查询操作,这个点的最近已标记祖先便为它所在并查集的根。
这个做法应该比线段树快,但不太好想到。


代码

(线段树版)

#include
#include
#include
using namespace std;const int N = 100005;struct node{ int v; node *next; }pool[N],*h[N];int cnt;void addedge(int u,int v){ node *p=&pool[++cnt]; p->v=v;p->next=h[u];h[u]=p; }int dep[N],dfn[N],out[N];int tot;void dfs(int u){ int v; dfn[u]=++tot; for(node *p=h[u];p;p=p->next) if(!dep[v=p->v]){ dep[v]=dep[u]+1; dfs(v); } out[u]=tot;}struct tree{ tree *ch[2]; int v; }pool2[N*20],*root;int cnt2;void pushdown(tree *p){ if(dep[p->v]>dep[p->ch[0]->v]) p->ch[0]->v=p->v; if(dep[p->v]>dep[p->ch[1]->v]) p->ch[1]->v=p->v; }void build(tree *p,int l,int r){ p->v=1; if(l==r) return; int mid=(l+r)>>1; build(p->ch[0]=&pool2[++cnt2],l,mid); build(p->ch[1]=&pool2[++cnt2],mid+1,r); }void change(tree *p,int l,int r,int L,int R,int c){ if(dep[c]
v]) return; if(l==L && r==R){ if(dep[c]>dep[p->v]) p->v=c; return; } pushdown(p); int mid=(l+r)>>1; if(mid>=R) change(p->ch[0],l,mid,L,R,c); else if(mid
ch[1],mid+1,r,L,R,c); else { change(p->ch[0],l,mid,L,mid,c); change(p->ch[1],mid+1,r,mid+1,R,c); }}int query(tree *p,int l,int r,int L){ if(l==L && r==L) return p->v; pushdown(p); int mid=(l+r)>>1; if(mid>=L) return query(p->ch[0],l,mid,L); return query(p->ch[1],mid+1,r,L); }int n,q;int main(){ char ch; int u,v; scanf("%d%d",&n,&q); for(int i=1;i

转载于:https://www.cnblogs.com/lindalee/p/8393513.html

你可能感兴趣的文章
<转>C#读取doc,pdf,ppt文件
查看>>
缓存帮助类
查看>>
VC++ 2010 创建高级Ribbon界面详解(1)
查看>>
未能加载文件或程序集“XXX”或它的某一个依赖项。试图加载格式不正确的程序...
查看>>
JS基础(四)运算符
查看>>
swing
查看>>
Continuous integration
查看>>
前端知识点总结
查看>>
github 在ubuntu 使用--常用命令
查看>>
PTA 5-3 解题报告
查看>>
ubuntu遇到包依赖问题出错的解决方法
查看>>
认识DOM 文档对象模型DOM(Document Object Model)定义访问和处理HTML文档的标准方法。元素、属性和文本的树结构(节点树)。...
查看>>
hl7 V2中Message Control ID的含义及应用
查看>>
IOS 4个容易混淆的属性(textAligment contentVerticalAlignment contentHorizontalAlignment contentMode)...
查看>>
iOS 修改textholder的颜色
查看>>
【资料】wod地城掉落
查看>>
C# FTPHelper(搬运)
查看>>
C#HttpHelper类1.3正式版教程与升级报告
查看>>
【转】Android 语言切换过程分析
查看>>
jpa 多对多关系的实现注解形式
查看>>