「BZOJ3306」树

题目

一棵有根点权树有 n 个结点,编号为 1 的结点是根节点,第 i 个结点的父亲结点为\(f_i\),权值为\(a_i\)。
接着进行 m 次以下操作:

  • V x y 表示将点 x 的权修改为 y;
  • E x 表示把有根树的根改为点 x;
  • Q x 表示查询点 x 的子树最小值。

Input Format
第一行两个整数 n,m。

接下来 n 行,每行两个整数\(f_i,v_i\)
接下来 m 行,每行一次操作。

Output Format
对于每次 Q 操作,输出子树最小值。

样例

算了样例太水了没啥参考性

输入:
7 6
0 4
1 9
1 9
2 8
3 6
2 1
1 9
E 5
Q 5
Q 1
V 3 6
V 2 1
E 6
输出:
1
1

解题思路

其实如果没有换根,这就是一个线段树维护的子树最大值。dfn序。

如果加上了换根,我们就要去考虑当前询问的节点 x 和root有什么关系,首先肯定是在原树上进行操作。

  1. Obviously,如果x==root,那么就直接query整棵树。这个先判断。
  2. Furthermore,如果当前的lca(x,root)!= x,那么x的答案与root无关,直接查询 x 即可。
  3. Last but not least,如果 lca(x,root)== x,那么查询的子树是除了x->root这一颗子树之外的所有。
点击查看代码
#include<bits/stdc++.h>
#define int long long 
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
const int N=1e5+10;
int n,cnt,h[N],ans,root=1,size[N],cow,dfn[N],f[N][25],val[N],dep[N],b[N],step,m;
struct st{
	int e,ne;
}a[N<<1];
struct tree{
	int k,mn;
}tr[N<<2];
void add(int x,int y){
	a[++cnt].e=y;
	a[cnt].ne=h[x];
	h[x]=cnt;
} 
void dfs(int k,int fa){
	dfn[k]=++step;
	size[k]=1;
	f[k][0]=fa;
	dep[k]=dep[fa]+1;
	for(int i=1;i<=20;i++) f[k][i]=f[f[k][i-1]][i-1];
	b[step]=val[k];
	for(int i=h[k];i;i=a[i].ne){
		if(a[i].e==fa) continue;
		dfs(a[i].e,k);
		size[k]+=size[a[i].e];
	}
}
void pushup(int rt){tr[rt].mn=min(tr[ls].mn,tr[rs].mn);}
void build(int rt,int l,int r){
	if(l==r){tr[rt]={b[l],b[l]};return ;}
	int mid=l+r>>1;build(ls,l,mid);build(rs,mid+1,r);pushup(rt);
}
void change(int rt,int l,int r,int k,int v){
	if(l==r){tr[rt]={v,v};return ;}
	int mid=l+r>>1;
	if(k<=mid) change(ls,l,mid,k,v);
	else change(rs,mid+1,r,k,v);
	pushup(rt);
}
int query(int rt,int l,int r,int x,int y){
	if(x>y || !x || !y) return 1e9;
	if(l>=x && r<=y) return tr[rt].mn;
	int mid=l+r>>1,s=1e9;
	if(x<=mid) s=min(s,query(ls,l,mid,x,y));
	if(y>mid) s=min(s,query(rs,mid+1,r,x,y));
	return s;
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=20;i>=0;i--){
		if(dep[f[x][i]]>=dep[y]) x=f[x][i];
	}
	if(x==y) return x;
	for(int i=20;i>=0;i--){
		if(f[x][i]!=f[y][i]){
			x=f[x][i];y=f[y][i];
		}
	}
	return f[x][0];
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++){
		int x,y;
		scanf("%lld%lld",&x,&val[i]);
		add(x,i);
		add(i,x);
	}
	dfs(1,0); 
	build(1,1,n);
	while(m--){
		char op;
		cin>>op;
		if(op=='V'){
			int x,y;
			scanf("%lld%lld",&x,&y);
			change(1,1,n,dfn[x],y);
		}
		if(op=='Q'){
			int x;
			scanf("%lld",&x);
			int fa=lca(x,root); 
			if(x==root){
				printf("%lld\n",query(1,1,n,1,n));
				continue; 
			} 
			if(fa!=x){
				printf("%lld\n",query(1,1,n,dfn[x],dfn[x]+size[x]-1));
			} 
			else{
				int p=root;
				if(dep[p]<dep[x]) swap(p,x);
				for(int i=20;i>=0;i--){
					if(dep[f[p][i]]>dep[x]){
						p=f[p][i];
					}
					if(f[p][0]==x) break;
				} 
				int o1=query(1,1,n,dfn[p]+size[p],n);
				int o2=query(1,1,n,dfn[1],dfn[p]-1);
				printf("%lld\n",min(o1,o2));
			}
		}
		if(op=='E') scanf("%lld",&root);
	}
}

碎碎念:
数组一定不要卡死开我真服了。
区间最值,查询之类的问题多想想线段树!!!
树上问题多想lca。
(最终解释权归16:36的脑子所有)

posted on 2026-09-30 16:40  FAYEEEEEEE  阅读(5)  评论(0)    收藏  举报

导航