BZOJ4551[Tjoi2016&Heoi2016]树——dfs序+线段树/树链剖分+线段树
题目描述
在2016年,佳媛姐姐刚刚学习了树,非常开心。现在他想解决这样一个问题:给定一颗有根树(根为1),有以下
两种操作:1. 标记操作:对某个结点打上标记(在最开始,只有结点1有标记,其他结点均无标记,而且对于某个
结点,可以打多次标记。)2. 询问操作:询问某个结点最近的一个打了标记的祖先(这个结点本身也算自己的祖
先)你能帮帮他吗?
输入
输入第一行两个正整数N和Q分别表示节点个数和操作次数接下来N-1行,每行两个正整数u,v(1≤u,v≤n)表示u到v
有一条有向边接下来Q行,形如“opernum”oper为“C”时表示这是一个标记操作,oper为“Q”时表示这是一个询
问操作对于每次询问操作,1 ≤ N, Q ≤ 100000。
输出
输出一个正整数,表示结果
样例输入
5 5
1 2
1 3
2 4
2 5
Q 2
C 2
Q 2
Q 5
Q 3
1 2
1 3
2 4
2 5
Q 2
C 2
Q 2
Q 5
Q 3
样例输出
1
2
2
1
2
2
1
这道题有两种做法(据说不止两种,本蒟蒻只会这两种qwq)。
先来讲第一种:dfs序+线段树。
假设我们对一个点进行了标记,那么可能会影响哪些点的答案?
没错就是它的子树中所有点,因为一个点的子树在dfs序上是一段区间,我们以dfs序建线段树,每个点维护影响这个区间的深度最大的标记点是谁,那么每次修改时区间修改子树区间,将标记永久化,查询时单点查询,在线段树上沿途的标记中取深度最大的就是答案。
再来说说比较麻烦的一种做法:树链剖分+线段树。
对于线段树的每个点维护区间中被打标记的最深的点,每次修改时单点修改,查询时利用树链剖分往上爬重链,查询重链在线段树上的区间,只要有答案就输出。
dfs序+线段树
#include<set> #include<map> #include<stack> #include<queue> #include<cmath> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m; int x,y; int tot; int num; char ch[2]; int f[100010]; int s[100010]; int d[100010]; int t[100010]; int to[200010]; int mx[800010]; int next[200010]; int head[100010]; void add(int x,int y) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; } void dfs(int x) { s[x]=++num; d[x]=d[f[x]]+1; for(int i=head[x];i;i=next[i]) { if(to[i]!=f[x]) { f[to[i]]=x; dfs(to[i]); } } t[x]=num; } int cmp(int x,int y) { if(d[x]>d[y]) { return x; } else { return y; } } void change(int rt,int l,int r,int L,int R,int k) { if(L<=l&&r<=R) { mx[rt]=cmp(mx[rt],k); return ; } int mid=(l+r)>>1; if(L<=mid) { change(rt<<1,l,mid,L,R,k); } if(R>mid) { change(rt<<1|1,mid+1,r,L,R,k); } } int query(int rt,int l,int r,int k) { if(l==r) { return mx[rt]; } int mid=(l+r)>>1; int res=mx[rt]; if(k<=mid) { return cmp(res,query(rt<<1,l,mid,k)); } else { return cmp(res,query(rt<<1|1,mid+1,r,k)); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(1); change(1,1,n,1,n,1); for(int i=1;i<=m;i++) { scanf("%s",ch); scanf("%d",&x); if(ch[0]=='C') { change(1,1,n,s[x],t[x],x); } else { printf("%d\n",query(1,1,n,s[x])); } } }
树链剖分+线段树
#include<set> #include<map> #include<stack> #include<queue> #include<cmath> #include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; int n,m; int x,y; int tot; int num; char ch[2]; int f[100010]; int s[100010]; int d[100010]; int q[100010]; int to[200010]; int mx[800010]; int son[100010]; int top[100010]; int size[100010]; int next[200010]; int head[100010]; void add(int x,int y) { tot++; next[tot]=head[x]; head[x]=tot; to[tot]=y; } void dfs(int x) { d[x]=d[f[x]]+1; size[x]=1; for(int i=head[x];i;i=next[i]) { if(to[i]!=f[x]) { f[to[i]]=x; dfs(to[i]); size[x]+=size[to[i]]; if(size[to[i]]>size[son[x]]) { son[x]=to[i]; } } } } void dfs2(int x,int tp) { s[x]=++num; q[num]=x; top[x]=tp; if(son[x]) { dfs2(son[x],tp); } for(int i=head[x];i;i=next[i]) { if(to[i]!=f[x]&&to[i]!=son[x]) { dfs2(to[i],to[i]); } } } int cmp(int x,int y) { if(d[x]>d[y]) { return x; } else { return y; } } void pushup(int rt) { mx[rt]=cmp(mx[rt<<1],mx[rt<<1|1]); } void change(int rt,int l,int r,int k,int v) { if(l==r) { mx[rt]=v; return ; } int mid=(l+r)>>1; if(k<=mid) { change(rt<<1,l,mid,k,v); } else { change(rt<<1|1,mid+1,r,k,v); } pushup(rt); } int query(int rt,int l,int r,int L,int R) { if(L<=l&&r<=R) { return mx[rt]; } int mid=(l+r)>>1; if(L>mid) { return query(rt<<1|1,mid+1,r,L,R); } if(R<=mid) { return query(rt<<1,l,mid,L,R); } else { return cmp(query(rt<<1,l,mid,L,R),query(rt<<1|1,mid+1,r,L,R)); } } int ask(int x) { int res; while(top[x]!=1) { res=query(1,1,n,s[top[x]],s[x]); if(res!=0) { return res; } x=f[top[x]]; } res=query(1,1,n,1,s[x]); return res; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y); add(y,x); } dfs(1); dfs2(1,1); change(1,1,n,1,1); for(int i=1;i<=m;i++) { scanf("%s",ch); scanf("%d",&x); if(ch[0]=='C') { change(1,1,n,s[x],x); } else { printf("%d\n",ask(x)); } } }