试题来源
IOI2012中国国家队训练
问题描述
给定一棵大小为 n 的有根点权树,支持以下操作:
• 换根
• 修改点权
• 查询子树最小值
• 换根
• 修改点权
• 查询子树最小值
输入格式
第一行两个整数 n, Q ,分别表示树的大小和操作数。
接下来n行,每行两个整数f,v,第i+1行的两个数表示点i的父亲和点i的权。保证f < i。如 果f = 0,那么i为根。输入数据保证只有i = 1时,f = 0。
接下来 m 行,为以下格式中的一种:
• V x y表示把点x的权改为y
• E x 表示把有根树的根改为点 x
• Q x 表示查询点 x 的子树最小值
接下来n行,每行两个整数f,v,第i+1行的两个数表示点i的父亲和点i的权。保证f < i。如 果f = 0,那么i为根。输入数据保证只有i = 1时,f = 0。
接下来 m 行,为以下格式中的一种:
• V x y表示把点x的权改为y
• E x 表示把有根树的根改为点 x
• Q x 表示查询点 x 的子树最小值
输出格式
对于每个 Q ,输出子树最小值。
样例输入
3 7
0 1
1 2
1 3
Q 1
V 1 6
Q 1
V 2 5
Q 1
V 3 4
Q 1
0 1
1 2
1 3
Q 1
V 1 6
Q 1
V 2 5
Q 1
V 3 4
Q 1
样例输出
1
2
3
4
2
3
4
数据规模和约定
对于 100% 的数据:n, Q ≤ 10^5。
题目大意就是给你一棵树,支持换根、修改点权、求一个点的子树的最小值。
对于这道题,如果没有换根操作,就直接上RMQ。
然而有了换根,我们可以先以1号点为根处理出这棵树的dfs序用线段树来维护一段dfs序的最小值,修改点权就不说了。
对于每个查询
设查询的点X;
设此时根为R;
设L[X]代表X在dfs序中第一次出现的位置
设R[X]代表X在dfs序中第一次出现的位置
如图的一棵树
1:当X等于R时 比如X=R=1
直接输出整棵树的最小值。
2:当R不在X的子树中 比如X=2 R=3
明显就是X的子树中的最小值
即为L[X]到R[X]的最小值
3:当X是R的祖先时
首先确定包含点的范围:
举两个例子:当X=2、R=4时是1、2、3、5
, 当X=1、R=4时是1、3。
通过这两个例子,我们可以看出包含的范围就是整棵树除去E所在的子树(E是X的儿子,也是R的祖先)。
那么答案就是L[1]到L[E]-1与R[E]+1到R[1]的最小值
(这一点要仔细想想,当初也是花了好久才弄懂)
最后说一下几个要注意的地方吧
求dfs序,不能直接dfs,会爆栈,所以。。。。手动栈吧
找E的时候不能一步一步往上跳,用倍增吧
Ps:其实上面两点都不要管就可以过原始数据,只是我们oj丧心病狂处处卡人啊。。。。
附代码
#include<cstdio> #include<iostream> #include<cmath> const double eps=0.0000001; const int maxn=100010*2,maxm=maxn*8; using namespace std; int tot=0; int pre[maxn],son[maxn]; int now[maxn]; void add(int a,int b){ pre[++tot]=now[a]; now[a]=tot; son[tot]=b; } inline int read(){ char ch=getchar(); int tmp=0; while (ch<'0' || ch>'9') ch=getchar(); for (;ch<='9' && ch>='0';ch=getchar()) tmp=tmp*10+ch-'0'; return tmp; } int n,m; int a[maxn]; void init(){ n=read(); m=read(); for (int i=1;i<=n;++i){ int a=read(); int b=read(); add(a,i); ::a[i]=b; } } int l[maxn],r[maxn]; int dep[maxn]; int sum=0; int maxdep=0; int fa[maxn][40]; int num[maxn]; int syssta[maxn]; int bre[maxn]; void dfs(){ int tot=0; syssta[++tot]=1; dep[1]=1; for (int i=1;i<=n;++i) bre[i]=now[i]; while (tot){ if (bre[syssta[tot]]==now[syssta[tot]]){ l[syssta[tot]]=++sum; num[sum]=a[syssta[tot]]; } if (!bre[syssta[tot]]){ r[syssta[tot]]=++sum; num[sum]=a[syssta[tot]]; tot--; } else { syssta[tot+1]=son[bre[syssta[tot]]]; fa[son[bre[syssta[tot]]]][0]=syssta[tot]; bre[syssta[tot]]=pre[bre[syssta[tot]]]; dep[syssta[tot+1]]=dep[syssta[tot]]+1; maxdep=max(maxdep,dep[syssta[tot+1]]); tot++; } } } void prepare(){ int quit=(int)(log(maxdep)/log(2)+eps); for (int i=1;i<=quit;++i) for (int j=1;j<=n;++j) fa[j][i]=fa[fa[j][i-1]][i-1]; } inline int find(int x,int root){ swap(x,root); int gap=dep[x]-dep[root]-1; while (gap){ int tmp=(int)(log(gap)/log(2)+eps); x=fa[x][tmp]; gap-=1<<tmp; } return x; } int tree[maxm]; void inline updata(int p){ int Ls=p*2,Rs=Ls+1; tree[p]=tree[Ls]<tree[Rs]?tree[Ls]:tree[Rs]; } int wh[maxn]; void build(int p,int l,int r){ if (l==r){ tree[p]=num[l]; wh[l]=p; return; } int mid=(l+r)/2; build(p*2,l,mid); build(p*2+1,mid+1,r); updata(p); } void ins(int p,int add){ tree[p]=add; p=p>>1; while (p){ updata(p); p=p>>1; } } inline int min(int a,int b){ return a<b?a:b; } int countit(int p,int l,int r,int fir,int las){ if (l==fir && r==las) return tree[p]; int mid=(l+r)/2; int Ls=p*2,Rs=Ls+1; if (las<=mid) return countit(Ls,l,mid,fir,las); if (fir>mid) return countit(Rs,mid+1,r,fir,las); int ans=countit(Ls,l,mid,fir,mid); ans=min(ans,countit(Rs,mid+1,r,mid+1,las)); return ans; } void work(){ dep[1]=1; dfs(); prepare(); n*=2; build(1,1,n); int root=1; while (m--){ char tmp[3]; scanf("%s",tmp); if (tmp[0]=='V'){ int a=read(); int b=read(); ins(wh[l[a]],b); ins(wh[r[a]],b); } if (tmp[0]=='E') root=read(); if (tmp[0]=='Q'){ int x=read(); int ans; if (x==root) ans=tree[1]; else if (l[x]>l[root] || r[x]<r[root]) ans=countit(1,1,n,l[x],r[x]); else{ int xx=find(x,root); ans=min(countit(1,1,n,1,l[xx]-1),countit(1,1,n,r[xx]+1,n)); } printf("%d\n",ans); } } } int main(){ init(); work(); return 0; }