#include <cstdio> #include <cstring> #include <string> #include <cmath> #include <iostream> #include <algorithm> #include <vector> #include <stack> #include <sstream> #include <map> #include <set> #include <queue> #include <stdlib.h> typedef long long ll; using namespace std; #define mem(a,b) memset(a,b,sizeof(a)) //k<<1(节点k的左子树下标) #define lson l,mid,rt<<1 //k<<1|1(节点k的右子树下标) #define rson mid+1,r,rt<<1|1 #define ll long long const int N=100005; int n,m,tot,cnt; int first[N]; ll sum[N<<2],w[N],wt[N]; int d[N],fa[N],siz[N],son[N],top[N],id[N],rk[N]; struct node //节点:这道题没有边权值,所以没有w(价值) { int v; //终点 int nex; }e[N<<1]; void adde(int u,int v) //链式向前星,tot从0开始存,没有边权值,所以不用存 { e[tot].v=v; //记录u节点的终点 e[tot].nex=first[u]; //表示与第i条边同起点的下一条边存储的位置 first[u]=tot++; } void init() { tot = 0; //tot记录链式向前星 cnt=0; memset(first,-1,sizeof(first)); //链式向前星的head数组,一般初始化为-1 memset(son,0,sizeof(son)); } void dfs1(int u,int pre,int depth) //处理 d,fa,siz,son 数组 -> 分别是深度、父亲、大小(包含自己本身)、重儿子节点 { d[u]=depth; //记录u节点的深度 -》深度 fa[u]=pre; //记录u的父亲节点 -〉父亲 siz[u]=1; //记录u的大小(这个点本身size=1) -》大小 for(int i=first[u] ; i!=-1 ; i=e[i].nex) //遍历i节点 { int v=e[i].v; //v是是与i节点连接的点 if(v==pre) continue; //如果v是i的父亲节点,直接continue dfs1(v,u,depth+1); //层次深度+1,以v为当前节点,u为父亲节点,深度+1 siz[u]+=siz[v]; //父亲节点的大小=本身的大小+子节点的大小 if(siz[v]>siz[son[u]]) //如果 子节点v的大小 比 父亲节点的重儿子节点的大小 大,那么更新重儿子节点 { son[u]=v; } } } void dfs2(int u,int t) //当前节点,重链顶端 ——》处理 top(当前节点链的顶端) id(保存树中每个节点剖分呢以后的新编号) rk(对应节点) wt 数组 { top[u]=t; //u节点的顶端是t /*id和rk正好相互对应*/ id[u]=++cnt; //保存树中每个节点剖分后的新编号 rk[cnt]=u; //保存当前dfs标号在树中所对应的节点 wt[cnt]=w[u]; //储存当前的价值 if(!son[u]) return; //如果是叶子节点(没有重儿子),直接返回 /*如果不是叶子节点,一定是有重儿子的。 我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/ dfs2(son[u],t); for(int i=first[u];i!=-1;i=e[i].nex) //对重儿子进行遍历过后,再对当前节点的其余儿子进行遍历 { int v=e[i].v; //v是与i节点相连的节点 if(v!=son[u] && v!=fa[u]) //如果v不是重儿子,且v不是u的父亲节点 { dfs2(v,v); //一个点位于轻链底端,那么他的top必然是自己本身 } } } //更新函数,这里是实现最大值,同理可以变成最小值,区间和等 void pushup(int rt) { sum[rt] = sum[rt<<1] + sum[rt<<1|1]; //当前节点的sum=两个儿子节点的sum之和 } /*每个叶子节点的值就是数组的值,每个非叶子节点的度都为2,且左右两个孩子分别存储父亲一半的区间。每个父亲的存储的值也就是两个孩子存储的值的最大值*/ //递归方式建树 void build(int l,int r,int rt) //建线段树,一开始l=1,r=n,rt=1,rt表示当前需要建立的节点 { if(l==r) //如果左端点==右端点,即为叶子节点,直接赋值即可 { sum[rt]=wt[l]; } else { int mid=(l+r)>>1; //m为中间点,左儿子的节点区间为[l,m],右儿子的节点区间为[m+1,r] build(lson); //递归构造左儿子节点 build(rson); //递归构造右儿子节点 pushup(rt); //更新父节点 } } ll query(int L,int R,int l,int r,int rt) // 查询[L,R]区间的和 ,l=1,r=n,rt=1 { if(l>=L && r<=R) return sum[rt]; //如果需要查询的区间在l和r之间,直接返回当前节点的sum值就可以了 int mid=(l+r)>>1; ll ans=0; if(L<=mid) ans+=query(L,R,lson); if(mid<R) ans+=query(L,R,rson); return ans; } void update(int L,int R,int l,int r,int rt) //[L,R]区间开根号 ,rt为节点下标 { if(sum[rt]==r-l+1) return; //区间内所有点都为1,就不用再往下更新了 if(l==r) //如果左端点==右端点,为叶子节点,直接更新它的值就可以了 { sum[rt]=(ll)sqrt(sum[rt]); // } else { int mid=(l+r)>>1; if(L<=mid) update(L,R,lson); //如果需要更新的节点在左子树区间 if(mid<R) update(L,R,rson); //如果需要更新的节点在右子树区间 pushup(rt); //更新父节点的值 } } void uprange(int x,int y) //操作1 : x到y的最短路径开根 { while(top[x]!=top[y]) //如果x和y的顶端不相等,那么进行更新 { if(d[top[x]]<d[top[y]]) swap(x,y); // 如果x顶端比y顶端深度要小(就是x在上面),交换x和y,就是保证x在下面 update(id[top[x]],id[x],1,n,1); // x顶端到x之间开根号 x=fa[top[x]]; // x=x顶端的父亲节点,x的值不断向上找,直到x和y拥有一样的顶端 } //最后x顶端和y顶端相等 if(d[x]>d[y]) swap(x,y); //如果x的深度大于y的深度,那么交换x和y的值,保证x在下面 update(id[x],id[y],1,n,1); //x和y之间开根号 } //查询操作,其实是个LCA,不过这里是用了top来进行加速,因为top可以直接跳转到该重链的起始节点,轻链没有起始节点之说,他的top就是它自己。 //需要注意的是,每次循环只能跳一次并且让节点深的那个来跳到top父亲节点的位置,避免两个一起跳从而擦肩而过 ll querange(int x,int y) //操作2 : 求x到y的最短路径的和 { ll ans=0; while(top[x]!=top[y]) //如果x和y的链顶端节点不相等,那么进行更新 { if(d[top[x]]<d[top[y]]) swap(x,y); //如果x顶端比y顶端深度要小(就是x在上面),交换x和y,就是保证x在下面(保证x的节点深) ans+=query(id[top[x]],id[x],1,n,1); //求 x的顶端 到 x 之间的和(因为x顶端的id一定比x的id小) x=fa[top[x]]; //x=x顶端的父亲节点 } //直到x和y拥有一样的顶端为止 if(d[x]>d[y]) swap(x,y); ans+=query(id[x],id[y],1,n,1); //ans += x到y区间和 return ans; } int main() { scanf("%d %d",&n,&m); //树上的节点 + 操作数量 init(); //初始化 for(int i=1;i<=n;i++) { scanf("%lld",&w[i]); //将第i个节点的值输入 } int u,v; for(int i=1;i<n;i++) //原来的树 { scanf("%d%d",&u,&v); //输入两个节点,将两个节点连接起来(双向边) adde(u,v); adde(v,u); } dfs1(1,0,1); //第一遍dfs ,从根节点1开始,父亲是0,深度是1 dfs2(1,1); //第二遍dfs ,从根节点1开始,重链顶端也是1 /*两遍dfs就是树链剖分的主要处理,通过dfs我们已经保证重链上各个节点dfs序连续。 那么可以想到,我们可以通过数据结构,以线段树为例,来维护一条重链的信息。*/ build(1,n,1); //建立线段树 int op,x,y; //op是哪个操作,x,y分别是操作的两个点 while(m--) { scanf("%d %d %d",&op,&x,&y); if(op==0) uprange(x,y); //开根号向下取整 else if(op==1) printf("%lld\n",querange(x,y)); //计算x到y之间的和 } return 0; }