【题目描述】
给定一棵大小为 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 的子树最小值
【输出】
对于每个 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
【样例输出】
1
2
3
4
【提示】
对于 100% 的数据:n, Q ≤ 10^5。
【思路】
对于这个题,我们可以写树链剖分。但是出于代码量和调试难度的考虑,我选择了dfs序来维护子树信息。对于维护子树最小值和单点修改,我们可以用线段树在dfs序上维护。那么目前的问题就在于换根。显然,我们不可能真正的换根重新dfs或者树链剖分一次。那么问题就在于利用原树上的信息来得到答案。对于维护子树和或者子树最小值的这一类问题,我们常常分三类情况讨论。
1.如果root不在u的子树内(lca(root,u)!=u),那么换根后的答案就是原树的答案。
2.如果root就是u(root==u),那么直接查询整颗子树。
3.如果root在u的子树内(lca(root,u)==u),我们发现root到u路径上经过的的最后一个节点v(也就是u的一个儿子)的原树中的子树是u换根后子树的以整棵树为全集的补集。也就是说,我们只要不考虑v的原树中的子树的前提下查询整棵树的其他部分就行了。如果是维护子树和,我们可以用整棵树的子树和减去v的子树的和。这里我们需要维护最值,不满足区间减法。因此我们考虑区间加法。显然在dfs序中,我们只需要合并区间[1,st[v]-1][ed[v]+1,n]就可以得到u换根后字数的答案。而对于v,我们可以在求lca(u,root)的时候顺便存下来。
代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<cmath>
#include<queue>
#define re register
#define lc (p<<1)
#define rc (p<<1|1)
using namespace std;
int n,m,a,b;
const int N=2e5+1;
int f[N];
int g[N];
int nxp[N<<2|1];
int cnt=0,tot=0;
struct ndeo{
int u,v;
}e[N];
int idx=0;
struct tree{
int l,r;
int mn;
}t[N];
inline void add(int u,int v)
{
e[++cnt].u=u;
e[cnt].v=v;
nxp[cnt]=f[u];
f[u]=cnt;
}
int fa[N][20];
int st[N];
int ed[N];
int dep[N];
int rev[N];
void dfs(int u)
{
st[u]=++idx;rev[idx]=u;
for(int re i=1;(1<<i)<=dep[u];i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int re i=f[u];i;i=nxp[i])
{
int v=e[i].v;
if(!dep[v])
{
dep[v]=dep[u]+1;
fa[v][0]=u;
dfs(v);
}
}
ed[u]=idx;
}
typedef pair<int,int>T;
T lca(int a,int b)
{
if(dep[a]<dep[b])swap(a,b);
int t=dep[a]-dep[b];t--;
for(int re i=0;(1<<i)<=t;i++)
if(t&(1<<i))a=fa[a][i];
if(fa[a][0]==b)return make_pair(fa[a][0],a);
for(int re i=18;i>=0;i--)
{
if(fa[a][i]!=fa[b][i])
{
a=fa[a][i];
b=fa[b][i];
}
}
return make_pair(fa[a][0],a);
}
char p;
int root=0;
void build(int p,int l,int r)
{
t[p].l=l;
t[p].r=r;
if(l==r)
{
t[p].mn=g[rev[l]];
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);
build(rc,mid+1,r);
t[p].mn=min(t[lc].mn,t[rc].mn);
}
void change(int p,int k,int v)
{
int l=t[p].l;
int r=t[p].r;
if(l==r)
{
t[p].mn=v;
return;
}
int mid=(l+r)>>1;
if(k<=mid)change(lc,k,v);
else if(k>mid)change(rc,k,v);
t[p].mn=min(t[lc].mn,t[rc].mn);
}
int ans=0x7f7f7f7f;
void query(int p,int ql,int qr)
{
if(qr<ql)return;
if(ql<=t[p].l&&t[p].r<=qr)
{
ans=min(ans,t[p].mn);
return;
}
int mid=(t[p].l+t[p].r)>>1;
if(ql<=mid)query(lc,ql,qr);
if(qr>mid)query(rc,ql,qr);
}
int main()
{
scanf("%d%d",&n,&m);
for(int re i=1;i<=n;i++)
{
scanf("%d%d",&a,&g[i]);
if(!a)
{
root=i;
continue;
}
add(a,i);
add(i,a);
}
dep[root]=1;
dfs(root);
build(1,1,idx);
for(int re i=1;i<=m;i++)
{
p=getchar();
while(p!='Q'&&p!='V'&&p!='E')p=getchar();
if(p=='V')scanf("%d%d",&a,&b),change(1,st[a],b);
else if(p=='E')scanf("%d",&root);
else
{
scanf("%d",&a);
if(root==a)
{
ans=0x7f7f7f7f;
query(1,1,idx);
printf("%d\n",ans);
continue;
}
T s=lca(a,root);
if(s.first!=a)
{
ans=0x7f7f7f7f;
query(1,st[a],ed[a]);
printf("%d\n",ans);
continue;
}
else
{
ans=0x7f7f7f7f;
query(1,1,st[s.second]-1);
int ans1=ans;
ans=0x7f7f7f7f;
query(1,ed[s.second]+1,idx);
printf("%d\n",min(ans,ans1));
}
}
}
}