题目链接:
http://172.25.37.251/problem/142
题目大意:
有一棵
n
n
n个节点的树, 初始时根节点为
1
1
1。现在要支持如下操作:
1.将某节点
x
x
x设置为根;
2.改变某节点
x
x
x权值;
3.询问以某节点
x
x
x为根的子树内节点权值之和;
4.询问以某两点想
x
x
x,
y
y
y为端点的链上的节点权值之和。
数据范围:
n
≤
100000
n \leq 100000
n≤100000
题目分析:
1.根据题目的要求,先按照根为
1
1
1来考虑所有情况。
2.然后分析操作
1
1
1对操作
3
3
3,
4
4
4的影响。我们可以发现,对于操作
3
3
3来说,当根不在子树内时,不影响子树和的答案。当根在子树内时,只有
x
→
r
o
o
t
x \rightarrow root
x→root的第一个节点所在的子树的答案不计入答案,其余的点都要计入答案。而其实改变根的值对操作
4
4
4是不影响的。
3.最后分析操作
2
2
2对操作
3
3
3,
4
4
4的影响。可以发现,对于操作
3
3
3非常简单,只需要维护一个数据结构,保存一下和就行了。而对于操作4我们需要分析其本质,当改变一个点的值时,受到影响的只有穿过这个点的所有链。而这些链都有一个共同的特点就是必定有一个点是节点
x
x
x的子树上的点。所以我们可以只用修改
x
x
x的子树即可。
4.但是如何在
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的时间内完成一系列操作。对于操作
3
3
3我们只需要维护一个简单的树状数组就可以解决问题。而对于操作
4
4
4就变得有些麻烦。我们还要分析清楚几个点:
- x x x,y都是 l c a lca lca的子树上的点。
- x → y x \rightarrow y x→y的路径上的点也是 l c a lca lca的子树上的点。
- 所以我们可以按照 d f s dfs dfs序维护一个树状数组来进行简单的区间修改单点查询。 g e t a n s ( x ) getans(x) getans(x)得到从 x x x出发,深度递减的链的权值, g e t a n s ( l c a ) getans(lca) getans(lca)得到从 l c a lca lca出发,深度递减的链的权值。相减就是 x x x到 l c a lca lca的权值。 y y y同理可得。
分析到此为止,本题维护两个树状数组,并通过二分寻找根所在子树就可以完美解决,下面附上正解代码。
正解代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
typedef long long ll;
const ll maxn=100010;
vector<ll> E[maxn];
ll n,Q;
ll d[maxn],parent[maxn][20];
ll dfn[maxn],times=0,son[maxn];
ll tree[2][maxn],val[maxn];
void dfs(ll u,ll pre)
{
dfn[u]=++times;
son[u]=1;
for(ll i=0;i<E[u].size();i++)
{
ll v=E[u][i];
if(v!=pre)
{
d[v]=d[u]+1;
parent[v][0]=u;
dfs(v,u);
son[u]+=son[v];
}
}
}
ll lowbit(ll x)
{
return x&(-x);
}
void change(ll flag,ll x,ll v)
{
for(ll i=x;i<=n;i+=lowbit(i))
tree[flag][i]+=v;
}
ll getans(ll flag,ll x)
{
if(x==0)
return 0;
ll res=0;
for(ll i=x;i;i-=lowbit(i))
res+=tree[flag][i];
return res;
}
ll LCA(ll x,ll y)
{
ll i,j;
if(d[x]<d[y])
swap(x,y);
for(i=0;(1<<i)<=d[x];i++);
i--;
for(j=i;j>=0;j--)
if(d[x]-(1<<j)>=d[y])
x=parent[x][j];
if(x==y)
return x;
for(j=i;j>=0;j--)
{
if(parent[x][j]!=parent[y][j])
{
x=parent[x][j];
y=parent[y][j];
}
}
return parent[x][0];
}
int main()
{
//freopen("test.txt","r",stdin);
scanf("%lld%lld",&n,&Q);
for(ll i=1;i<=n-1;i++)
{
ll u,v;
scanf("%lld%lld",&u,&v);
E[u].push_back(v);
E[v].push_back(u);
}
d[1]=0;
dfs(1,-1);
for(ll level=1;(1<<level)<20;level++)
for(ll i=1;i<=n;i++)
parent[i][level]=parent[parent[i][level-1]][level-1];
for(ll i=1;i<=n;i++)
{
scanf("%lld",&val[i]);
change(0,dfn[i],val[i]);
change(1,dfn[i],val[i]);
change(1,dfn[i]+son[i],-val[i]);
}
ll root=1;
while(Q--)
{
ll op,x,y;
scanf("%lld%lld",&op,&x);
if(op==1)
root=x;
else if(op==2)
{
scanf("%lld",&y);
change(0,dfn[x],y-val[x]);
change(1,dfn[x],y-val[x]);
change(1,dfn[x]+son[x],val[x]-y);
val[x]=y;
}
else if(op==3)
{
if(root==x)
{
printf("%lld\n",getans(0,n));
continue;
}
else if(dfn[root]<dfn[x] || dfn[root]>=dfn[x]+son[x])
{
printf("%lld\n",getans(0,dfn[x]+son[x]-1)-getans(0,dfn[x]-1));
continue;
}
ll l=0,r=E[x].size()-1;
while(l<r)
{
ll mid=(l+r)>>1;
ll v=E[x][mid];
if(v==parent[x][0])
{
v=E[x][mid+1];
if(dfn[root]<dfn[v])
r=mid-1;
else
l=mid+1;
}
else
{
if(dfn[root]<=dfn[v]+son[v]-1)
r=mid;
else
l=mid+1;
}
}
ll v=E[x][l];
printf("%lld\n",getans(0,n)+getans(0,dfn[v]-1)-getans(0,dfn[v]+son[v]-1));
}
else
{
scanf("%lld",&y);
ll ancs=LCA(x,y);
printf("%lld\n",getans(1,dfn[x])+getans(1,dfn[y])-2*getans(1,dfn[ancs])+val[ancs]);
}
}
return 0;
}