题目描述
一棵树上有n个节点,编号分别为1到n,每个节点都有一个权值w。
我们将以下面的形式来要求你对这棵树完成一些操作:
I. CHANGE u t : 把结点u的权值改为t
II. QMAX u v: 询问从点u到点v的路径上的节点的最大权值
III. QSUM u v: 询问从点u到点v的路径上的节点的权值和
注意:从点u到点v的路径上的节点包括u和v本身
输入格式:
输入文件的第一行为一个整数n,表示节点的个数。
接下来n – 1行,每行2个整数a和b,表示节点a和节点b之间有一条边相连。
接下来一行n个整数,第i个整数wi表示节点i的权值。
接下来1行,为一个整数q,表示操作的总数。
接下来q行,每行一个操作,以“CHANGE u t”或者“QMAX u v”或者“QSUM u v”的形式给出。
输出格式:
对于每个“QMAX”或者“QSUM”的操作,每行输出一个整数表示要求输出的结果。
输入样例
4
1 2
2 3
4 1
4 2 1 3
12
QMAX 3 4
QMAX 3 3
QMAX 3 2
QMAX 2 3
QSUM 3 4
QSUM 2 1
CHANGE 1 5
QMAX 3 4
CHANGE 3 6
QMAX 3 4
QMAX 2 4
QSUM 3 4
输出样例
4
1
2
2
10
6
5
6
5
16
说明
对于100%的数据,保证1<=n<=30000,0<=q<=200000;中途操作中保证每个节点的权值w在-30000到30000之间。
题目分析:
树链剖分入门
首先树链剖分两个dfs与处理好
然后就直接用预处理出的编号建线段树
对于CHANGE操作
直接在线段树上对num[u]进行单点修改
对于QSUM操作
我们先比较top[u]和top[v]是否相同(即两者是否在同一条重链上)
若不在,我们设dep[u]较大
我们先对ll=num[ top[u] ]到rr=num[u]这段查询区间和,并以ans记录
然后另u=fa[ top[u] ],重复上述操作
若top[u]和top[v]相同
设dep[u] < dep[v]
再次ans+=ll=num[u]到rr=num[u]的区间和
然后返回ans
QMAX操作也是类似
具体解释请看代码注释
#include<iostream>
#include<cstdio>
#include<vector>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
return f*x;
}
void print(int x)
{
if(x<0){putchar(‘-‘);x=-x;}
if(x>9)print(x/10);
putchar(x%10+‘0‘);
}
int n,t;
int tot;
struct node{int v,nxt;}E[100010];
int head[100010];
int w[100010];
int cnt;
int dep[100010],fa[100010];
int size[100010],son[100010];
int top[100010],num[100010],pre[100010];
int sum[400010],maxn[400010];
char ss[20];
void add(int u,int v)
{
E[++tot].v=v;
E[tot].nxt=head[u];
head[u]=tot;
}
void dfs1(int u,int pa)
{
size[u]=1;
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==pa) continue;
dep[v]=dep[u]+1; fa[v]=u;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
num[u]=++cnt; pre[cnt]=u; top[u]=tp;
if(son[u]) dfs2(son[u],tp);
for(int i=head[u];i;i=E[i].nxt)
{
int v=E[i].v;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
void push(int p)
{
maxn[p]=max(maxn[p<<1],maxn[p<<1|1]);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void build(int s,int t,int p)
{
if(s==t){ maxn[p]=sum[p]=w[ pre[s] ]; return; }
int mid=(s+t)>>1;
build(s,mid,p<<1); build(mid+1,t,p<<1|1);
push(p);
}
void update(int u,int w,int s,int t,int p)
{
if(s==t){maxn[p]=sum[p]=w;return;}
int mid=(s+t)>>1;
if(u<=mid) update(u,w,s,mid,p<<1);
else update(u,w,mid+1,t,p<<1|1);
push(p);
}
int getmax(int ll,int rr,int s,int t,int p)
{
if(ll<=s&&t<=rr) return maxn[p];
int mid=(s+t)>>1;
int ans=-1e9;
if(ll<=mid) ans=max(ans, getmax(ll,rr,s,mid,p<<1) );
if(rr>mid) ans=max(ans, getmax(ll,rr,mid+1,t,p<<1|1) );
return ans;
}
int qmax(int u,int v)
{
int ans=-1e9;
while (top[u]!=top[v])
{
if (dep[top[u]]<dep[top[v]])swap(u,v);
ans=max( ans,getmax(num[top[u]],num[u],1,n,1) );
u=fa[top[u]];
}
if (dep[u]<dep[v])swap(u,v);
ans=max(ans,getmax(num[v],num[u],1,n,1));
return ans;
}
int getsum(int ll,int rr,int s,int t,int p)
{
if(ll<=s&&t<=rr) return sum[p];
int mid=(s+t)>>1;
int ans=0;
if(ll<=mid) ans+=getsum(ll,rr,s,mid,p<<1) ;
if(rr>mid) ans+=getsum(ll,rr,mid+1,t,p<<1|1) ;
return ans;
}
int qsum(int u,int v)
{
int ans=0;
while(top[u]!=top[v])//若u和v不在一条重链上
{
if( dep[ top[u] ] < dep[ top[v] ]) swap(u,v);//取top深度较大的一方
ans+=getsum(num[top[u]],num[u],1,n,1);//更新u到其top
u=fa[top[u]];//另u跳到其top的父亲
}
if(dep[u]<dep[v]) swap(u,v);//在一条重链上,直接区间更新
ans+=getsum(num[v],num[u],1,n,1);
return ans;
}
int main()
{
n=read();
for(int i=1;i<n;i++)
{
int u=read(),v=read();
add(u,v);add(v,u);
}
for(int i=1;i<=n;i++)
w[i]=read();
dep[1]=1; fa[1]=1;
dfs1(1,-1); dfs2(1,1);
build(1,n,1);//预处理——树剖
t=read();
while(t--)
{
scanf("%s",&ss);
int x=read(),y=read();
if(ss[1]==‘H‘) update(num[x],y,1,n,1);//直接更新num[x]
else if(ss[1]==‘M‘) print(qmax(x,y)),printf("\n");
else if(ss[1]==‘S‘) print(qsum(x,y)),printf("\n");
}
return 0;
}
题目描述
有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:操作 1 :把某个节点 x 的点权增加 a 。操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 a 。操作 3 :询问某个节点 x 到根的路径中所有点的点权和。
输入格式:
第一行包含两个整数 N, M 。表示点数和操作数。接下来一行 N 个整数,表示树中节点的初始权值。接下来 N-1 行每行两个正整数 from, to , 表示该树中存在一条边 (from, to) 。再接下来 M 行,每行分别表示一次操作。其中第一个数表示该操作的种类( 1-3 ) ,之后接这个操作的参数( x 或者 x a ) 。
输出格式:
对于每个询问操作,输出该询问的答案。答案之间用换行隔开。
输入样例
5 5
1 2 3 4 5
1 2
1 4
2 3
2 5
3 3
1 2 1
3 5
2 1 2
3 3
输出样例
6
9
13
说明
对于 100% 的数据, N,M<=100000 ,且所有输入数据的绝对值都不
会超过 10^6 。
题目分析:
这题与上面唯一不同的是要更新u的所有子树
不难发现其实u及其所有子节点
在线段树上的编号
就是ll=num[u]到rr=num[u]+size[u]-1的连续区间
知道这点其实就不难做了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long lt;
lt read()
{
lt f=1,x=0;
char ss=getchar();
while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
return x*f;
}
void print(lt x)
{
if(x<0){putchar(‘-‘);x=-x;}
if(x>9)print(x/10);
putchar(x%10+‘0‘);
}
lt n,m;
lt d[100010];
struct node{lt v,nxt;}E[200010];
lt head[100010];
lt cnt,tot;
lt dep[100010],fa[100010],son[100010];
lt top[100010],size[100010];
lt num[100010],pos[100010];
lt sum[1000010],add[1000010];
void adde(lt u,lt v)
{
E[++tot].nxt=head[u];
E[tot].v=v;
head[u]=tot;
}
void dfs1(lt u,lt pa)
{
size[u]=1;
for(lt i=head[u];i;i=E[i].nxt)
{
lt v=E[i].v;
if(v==pa) continue;
dep[v]=dep[u]+1; fa[v]=u;
dfs1(v,u);
size[u]+=size[v];
if(size[v]>size[son[u]]) son[u]=v;
}
}
void dfs2(lt u,lt tp)
{
num[u]=++cnt; pos[cnt]=u; top[u]=tp;
if(son[u]) dfs2(son[u],tp);
for(lt i=head[u];i;i=E[i].nxt)
{
lt v=E[i].v;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
}
}
void push(lt mid,lt s,lt t,lt p)
{
add[p<<1]+=add[p]; add[p<<1|1]+=add[p];
sum[p<<1]+=add[p]*(mid-s+1);
sum[p<<1|1]+=add[p]*(t-mid);
add[p]=0;
}
void build(lt s,lt t,lt p)
{
if(s==t){ sum[p]=d[ pos[s] ]; return; }
lt mid=(s+t)>>1;
build(s,mid,p<<1); build(mid+1,t,p<<1|1);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void update(lt u,lt w,lt s,lt t,lt p)
{
if(s==t){sum[p]+=w;return;}
lt mid=(s+t)>>1;
if(add[p])push(mid,s,t,p);
if(u<=mid) update(u,w,s,mid,p<<1);
else update(u,w,mid+1,t,p<<1|1);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
void uprange(lt ll,lt rr,lt s,lt t,lt p,lt w)
{
if(ll<=s&&t<=rr){sum[p]+=(t-s+1)*w;add[p]+=w;return;}
lt mid=(s+t)>>1;
if(add[p])push(mid,s,t,p);
if(ll<=mid)uprange(ll,rr,s,mid,p<<1,w);
if(rr>mid)uprange(ll,rr,mid+1,t,p<<1|1,w);
sum[p]=sum[p<<1]+sum[p<<1|1];
}
lt getsum(lt ll,lt rr,lt s,lt t,lt p)
{
if(ll<=s&&t<=rr) return sum[p];
lt mid=(s+t)>>1;
if(add[p])push(mid,s,t,p);
lt ans=0;
if(ll<=mid) ans+=getsum(ll,rr,s,mid,p<<1) ;
if(rr>mid) ans+=getsum(ll,rr,mid+1,t,p<<1|1) ;
return ans;
}
lt qsum(lt u,lt v)
{
lt ans=0;
while(top[u]!=top[v])
{
if( dep[ top[u] ] < dep[ top[v] ]) swap(u,v);
ans+=getsum(num[top[u]],num[u],1,n,1);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans+=getsum(num[u],num[v],1,n,1);
return ans;
}
int main()
{
n=read();m=read();
for(lt i=1;i<=n;i++)
d[i]=read();
for(lt i=1;i<n;i++)
{
lt u=read(),v=read();
adde(u,v);adde(v,u);
}
dep[1]=1; fa[1]=1;
dfs1(1,-1); dfs2(1,1);
build(1,n,1);
while(m--)
{
lt k=read(),u=read();
if(k==1){ lt x=read(); update(num[u],x,1,n,1); }
else if(k==2) { lt x=read(); uprange(num[u],num[u]+size[u]-1,1,n,1,x); }
else if(k==3) { print(qsum(u,1)); printf("\n"); }
}
return 0;
}