与树相关的数据结构
并查集UFS
描述和定义
在处理图论问题中常常会涉及到添加边的操作,而此时为了方便地得到此时图的连通状况,我们就需要并查集来维护图中节点的连通状况。并查集顾名思义,即支持对集合进行合并和查询的数据结构。为了便于描述给出如下定义
在图 G ( V , E ) G(V,E) G(V,E)中,初始状态可视为 E = ∅ E=\empty E=∅,并且定义连通的点为一个集合。对于节点 u u u,我们定义函数 f ( u ) f(u) f(u)表示u节点第一次合并入另一个集合时该集合的代表节点,集合的代表结点属于集合本身,连通块中节点的代表节点相同,当集合 A A A并入集合 B B B时,新集合的代表结点与 B B B一致。初始状态时,设 ∀ u ∈ V , f ( u ) = u \forall u\in V,f(u)=u ∀u∈V,f(u)=u;并操作的本质是对 f f f函数的维护;查操作是通过 f f f函数来判断节点是否连通;
基本实现
合并操作
当前的图中增加一条边,且这条边连通了两个连通块 A , B A,B A,B时,我们就需要将 A , B A,B A,B连通块合并,也就是维护 f f f函数,此时只需要修改 A A A中代表节点的 f f f函数值,把其代表值改为 B B B集合的代表结点。
void union(int a,int b){
int f1=findR(a),f2=findR(b);
if(f1!=f2)
f[f1]=f2;
return;
}
查询操作
对于节点 u , v u,v u,v我们只需要查询各自的代表结点,若代表结点不同就不连通,反之则连通;当查询节点 u u u所在集合的代表结点时,我们可以通过如下操作来获取代表结点:
int findR(int x){
int s=x;
while(s!=f[s]) s=f[s];
/*
while(x!=f[x]){
int mid=x;
x=f[x];
f[mid]=s;
}
此过程为路径压缩,因为每次查找只需要得到树的根节点,故可以直接将树的所有节点直接连向根节点
*/
return s;
}
正确性说明
初始状态时每个节点 u u u都是一个独立的集合,其代表节点即本身;合并操作可以看做建树的过程, A A A并入 B B B,可视作 A A A代表结点成为 B B B代表结点的子节点,故 f f f函数值代表的节点的父节点;
带权并查集
带权并查集在并查集的基础上增加了点之间的权值,所以在压缩路径时便需要考虑更新权值。
二叉树
二叉树是一种特殊的树,它满足每个节点至多两个子节点。我们可以通过以下有趣的定义来使得二叉树实现特定的功能。
堆Heap
堆是一棵完全二叉树,它满足父节点权值小于等于子节点权值存在大小关系(为了方便起见,假设为小根堆),利用其是完全二叉树的特性,我们可以用数组的下标来表征父子关系。
struct Heap{
int size;//记录堆中元素个数
int line[N];//N个数
void insert(int x){
line[++size]=x;
int now=size;
while(now!=1&&line[now]<line[now/2]) {
swap(line[now],line[now/2]);
now=now/2;
}
}
void pop(){
swap(line[1],line[size--]);
int now=1;
while((now<<1)<=size){
int s[2];
s[0]=line[now*2];
s[1]=line[now*2+1];
if(now*2+1>size) s[1]=0x7ffffff;
if(now<=s[0]&&now<=s[1]) break;
int p=s[0]>s[1];
swap(line[now],line[now*2+p]);
now=now*2+p;
}
}
}
二叉查找树BST
二叉查找树满足,父节点权值大于等于右子节点权值,小于左节点权值,其可以较为方便的查询某个节点的排名。但其时间复杂度不稳定,最坏情况下可能退化为链,单词操作复杂度为O(n)
struct BST{
int f[N],val[N],tot[N];//f[N]记录节点的父节点,val[N]记录节点权值,tot[N]记录以节点为根的子树中节点数量
int son[N][2]; //记录儿子节点
int size,root;//szie记录当前节点总数,root记录当前根节点
int cnt;
void newNode(int v,int fa){
++size;++cnt;
son[cnt][0]=son[cnt][1]=-1;
f[cnt]=fa;val[cnt]=v;tot[cnt]=1;
return;
}
void insert(int x){
if(size==0){
newNode(x,cnt+1);
root=cnt;
return;
}
int now=root;
while(1){
++tot[now];
int op=x>val[root];
if(son[now][op]==-1){
newnode(x,now);
son[now][op]=cnt;
break;
}
now=son[now][op];
}
}
void delete(int x){
int now=root;
while(now!=-1&&val[now]!=x)
now=son[now][x>val[now]];
if(now==-1) return;
--size;
if(now==root){
int kp=son[root][1];
if(kp==-1){
root=son[root][0];
return;
}
while(son[kp][0]!=-1) {
if(son[now][0]!=-1) tot[kp]+=tot[son[now][0]];
kp=son[kp][0];
}
son[kp][0]=son[root][0];
f[son[root][0]]=kp;
root=son[root][1];
return;
}
int op=x>val[f[now]];
if(son[now][op^1]==-1){
son[f[now]][op]=son[now][op];
f[son[now][op]]=f[now];
}
else{
son[f[now]][op]=son[now][op^1];
f[son[now][op^1]]=f[now];
int kp=son[now][op^1];
while(son[kp][op]!=-1){
if(son[now][op]!=-1) tot[kp]+=tot[son[now][op]];
kp=son[kp][op];
}
son[kp][op]=son[now][op];
f[son[now][op]]=kp;
}
while(now!=f[now]) --tot[now=f[now]];
return;
}
int lower_bound(int x){
int ans=-1,now=root;
while(now!=-1){
if(val[now]<=x) {
ans=max(val[now],ans);
now=son[now][1];
}
else now=son[now][0];
}
return ans;
}
}
平衡树
伸展树splay
为了克服BST可能退化为链的缺点,我们引入了旋转操作,通过旋转操作我们可以让树更加的平衡。在实现上,我们只增加了对每次访问到的节点的旋转到根的操作。
struct Splay{
int son[N][2];//记录左右儿子节点
int f[N],val[N],cnt[N],sub[N];//f[N]记录父节点,val[N]记录节点的权值,cnt[N]记录每个节点上重复的次数,sub[N]记录以节点为根的子树中节点个数(包括重复的节点)
int root,size,id;//root记录根节点的编号,size记录树中节点的总数(不包括重复的节点),id编号;
int getDir(int x){return val[x]>val[f[x]];}
void newnode(int v,int fa){
++size;++id;
son[id][0]=son[id][1]=0;
f[id]=fa;val[id]=v;cnt[id]=1;sub[id]=1;
return;
}
void setson(int s,int fa,int p){son[fa][p]=s;f[s]=fa;}
void update(itn s){sub[s]=sub[son[s][0]]+sub[son[s][1]]+cnt[s];}
void rotate(int s){
int a1=getDir(s),a2=getDir(f[s]),fa=f[s],gfa=f[f[s]];
setson(s,gfa,a2);
setson(son[s][!a1],fa,a1);
setson(fa,s,!a1);
update(f);update(s);
}
void splay(int s,int p){
for(;f[s]!=p;rotate(s))
if(f[f[s]]!=p&&getDir(s)==getDir(f[s])) rotate(f[s]);
root=s;
}
void insert(int x){
if(size==0){
newnode(x,0);
root=id;
return;
}
int now=root,flag=0;
while(x!=val[now]){
int op=x>val[now];
++sub[now];
if(!son[now][op]){
newnode(x,now);
setson(id,now,op);
flag=1;
}
now=son[now][op];
}
if(!flag){++cnt[now];++sub[now];}
splay(now,0);
}
int lower_bound(int x){
int minn=-1,op=-1,now=root;
while(now){
if(val[now]<x&&val[now]>minn){
op=now;minn=val[now];
}
if(val[now]>=x) now=val[now][0];
else now=val[now][1];
}
return op;
}
int upper_bound(int x){
int maxn=0x7ffffff,op=-1,now=root;
while(now){
if(val[now]>x&&val[now]<maxn){
op=now;maxn=val[now];
}
if(val[now]<=x) now=val[now][1];
else now=val[now][0];
}
return op;
}
void delete(int x){
if(size==0) {
printf("The tree is empty!!\n");
return;
}
int pre=lower_bound(x),nex=upper_bound(x);
if(pre<0||nex<0) {
int op=pre>0;
splay(pre<0?nex:pre,0);
if(!son[root][op]||val[son[root][op]]!=x){
printf("Can't find it\n");
return;
}
--cnt[son[root][op]];--cnt[root];
--sub[son[root][op]];--sub[root];
if(!cnt[son[root][op]]){
--size;
son[root][op]=0;
}
}
else{
splay(pre,0);splay(nex,root);
if(!son[nex][0]){
printf("Can't find it\n");
return;
}
--cnt[son[nex][0]];--cnt[nex];--cnt[root];
--sub[son[nex][0]];--sub[nex];--sub[root];
if(!cnt[son[nex][0]]){
--size;
son[nex][0]=0;
}
}
return;
}
}
Treap
Treap相当于给BST上的结点引入了一组随机数据,并要求BST上结点拥有的随机数据满足堆的性质,以此来使得BST更加平衡,理论上,按照如此操作我们得到的树的深度的期望值是 log n \log n logn的。与Splay相比,Treap的代码更简单,其旋转操作只有左旋和右旋两种情况。
struct Treap{
int sz,tim,root;//sz记录树中总的结点个数(不重复),tim为插入计数器,root记录根节点
int siz[N],num[N],val[N],rd[N],f[N],son[N][2];//siz记录子树大小(含重复节点),num记录结点累计次数,val记录结点权值,rd记录结点用于堆结构的随机数据,f记录父节点,son记录子节点
int getDir(int x){return val[x]>val[f[x]];}
void setson(int s,int fa,int op){son[fa][op]=s;f[s]=fa;}
void update(s){siz[s]=siz[son[s][0]]+siz[son[s][1]]+num[s];}
void newNode(int v,int fa){
++sz;++tim;
siz[tim]=num[tim]=1;val[tim]=v;rd[tim]=rand();
son[tim][0]=son[tim][1]=0;
if(fa!=0) setson(tim,fa,v>val[fa]);
else f[tim]=fa;
}
void rotate(int s){
int a1=getDir(s),a2=getDir(f[s]),fa=f[s],gfa=f[f[s]];
setson(s,gfa,a2);
setson(son[s][!a1],f,a1);
setson(f,s,!a1);
update(f);update(s);
}
void insert(int x){
if(!sz){newNode(x,0);root=tim;return;}
int now=root,flag=0;
while(x!=val[now]){
int op=x>val[now];
++siz[now];
if(!son[now][op]){newNode(x,now);flag=1;}
now=son[now][op];
}
if(!flag) ++num[now],++siz[now];
else
while(f[now]&&rd[now]<rd[f[now]]) rotate(now);
}
void delete(int x){
int now=root;
if(!sz) return ;
while(now&&val[now]!=x) {
--siz[now];
now=son[now][x>val[now]];
}
if(now){
--num[now];--siz[now];
if(!num[now]){
--sz;
int& ls=son[now][0],rs=son[now][1];
while(ls+rs)
rotate(rd[ls]<rd[rs]?ls:rs);
son[f[now]][getDir(now)]=0;
}
}
}
}
区间树形结构
线段树SegmentTree
线段树适用于对离散序列进行整段操作线段树支持各种各样的区间操作,包括区间加,区间乘,区间变为相同值等,支持的查询包括区间最值,区间和、区间积等,线段树的核心是明确要维护的参量,以及维护的方法,其中lazy标记相关的操作尤为重要。
struct Node{
int l,r,sum,maxn,minn,plazy,mlazy;
}
struct segmentTree{
int line[N],node[N<<2];//line存储初始数据,node存入线段树节点
void putDownSon(int s,int ls){
node[ls].plazy=node[ls].plazy*node[s].mlazy+node[s].plazy;
node[ls].maxn=node[ls].maxn*node[s].mlazy+node[s].plazy;
node[ls].minn=node[ls].minn*node[s].mlazy+node[s].plazy;
node[ls].sum=node[ls].sum*node[s].mlazy+node[s].plazy*(node[ls].r-node[ls].l+1);
node[ls].mlazy*=node[s].mlazy;
}
void putDown(int s){
putDownSon(s,s<<1);putDownSon(s,s<<1|1);
node[s].plazy=0;node[s].mlazy=1;
}
void update(int s){
node[s].sum=node[s<<1].sum+node[s<<1|1].sum;
node[s].maxn=max(node[s<<1].maxn,node[s<<1|1].maxn);
node[s].minn=min(node[s<<1].minn,node[s<<1|1].minn);
return;
}
void build(int s,int l,int r){
node[s].l=l;node[s].r=r;node[s].plazy=0;node[s].mlazy=1;
if(l==r){
node[s].sum=node[s].minn=node[s].maxn=line[l];
return;
}
int mid=l+r>>1;
build(s<<1,l,mid);build(s<<1|1,mid+1,r);
update(s);
}
void segmentAdd(int s,int l,int r,int k){
if(node[s].l>r||node[s].r<l) return;
if(l<=node[s].l&&node[s].r<=r){
node[s].plazy+=k;
node[s].maxn+=k;node[s].minn+=k;
node[s].sum+=k*(node[s].r-node[s].l+1);
return;
}
putDown(s);
segmentAdd(s<<1,l,r,k);segmentAdd(s<<1|1,l,r,k);
update(s);
}
void segmentMulti(int s,int l,int r,int k){
if(node[s].l>r||node[s].r<l) return;
if(l<=node[s].l&&node[s].r<=r){
node[s].mlazy*=k;node[s].plazy*=k;
node[s].maxn*=k; node[s].minn*=k;
node[s].sum*=k;
return;
}
putDown(s);
segmentMulti(s<<1,l,r,k);segmentMulti(s<<1|1,l,r,k);
update(s);
}
int askMax(int s,int l,int r){
if(node[s].l>r||node[s].r<l) return -1;
if(l<=node[s].l&&node[s].r<=r) return node[s].maxn;
putDown(s);
return max(askMax(s<<1,l,r),askMax(s<<1|1,l,r));
}
int askMin(int s,int l,int r){
if(node[s].l>r||node[s].r<l) return -1;
if(l<=node[s].l&&node[s].r<=r) return node[s].minn;
putDown(s);
return min(askMin(s<<1,l,r),askMin(s<<1|1,l,r));
}
int askSum(int s,int l,int r){
if(node[s].l>r||node[s].r<l) return 0;
if(l<=node[s].l&&node[s].r<=r) return node[s].sum;
putDown(s);
return askSum(s<<1,l,r)+askSum(s<<1|1,l,r);
}
}
树状数组BIT
树状数组本质上是线段树,其每一个节点表示的也是一段区间,但每个节点的子节点不一定只有两个。其中父子关系是由节点编号的二进制决定的,每个节点的子节点个数与节点编号二进制位中后导0的个数相同,并且每个节点表示的区间就是以节点编号为上界长度为 2 n 2^n 2n的区间,n为后导0的个数。但是直接对于原序列进行维护,我们无法做到快速的区间修改,所以在需要区间操作时,我们选择维护差分序列,将区间修改操作转化为单点修改,但我们的查询操作也需要修改,我们要多维护一个序列,以使得我们可以快速计算出区间和。
设原序列为 a 1 , a 2 , a 3 , . . . . a n a_1,a_2,a_3,....a_n a1,a2,a3,....an,我们增加一个 a 0 = 0 a_0=0 a0=0,引入差分序列 c 1 , c 2 , c 3 , . . . c n c_1,c_2,c_3,...c_n c1,c2,c3,...cn,其中 c i = a i − a i − 1 c_i=a_i-a_{i-1} ci=ai−ai−1,我们考虑用 c i c_i ci来表示 a i a_i ai的前缀和,
∑ i = 1 k a i = ∑ i = 1 k ∑ j = 1 i c j = ∑ i = 1 k ( k − i + 1 ) c i = k ∗ ∑ i = 1 k c i − ∑ i = 1 k ( i − 1 ) c i \sum _{i=1}^{k}a_i=\sum _{i=1}^{k}\sum_{j=1}^{i}c_j=\sum _{i=1}^{k}(k-i+1)c_i=k*\sum _{i=1}^{k}c_i-\sum _{i=1}^{k}(i-1)c_i ∑i=1kai=∑i=1k∑j=1icj=∑i=1k(k−i+1)ci=k∗∑i=1kci−∑i=1k(i−1)ci,故我们需要维护一个 c i c_i ci序列和 ( i − 1 ) c i (i-1)c_i (i−1)ci序列来计算得到我们需要的前缀和。
代码如下:
struct BIT{
int n;//记录数组元素个数
int a[N],c[N][2];//a记录原始数据,c[][0]维护差分序列,c[][1]维护(i-1)ci序列
int lowbit(int x){return x&(-x);}
void addPoint(int s,int x,int p){for(;s<=n;s+=lowbit(s)) c[s][p]+=x;}
int getSum(int x,int p){
int sum=0;
for(;x>0;x-=lowbit(x)) sum+=c[x][p];
return sum;
}
void addSegment(int l,int r,int x){
addPoint(l,x,0);addPoint(l,x*(l-1),1);
addPoint(r+1,-x,0);addPoint(r+1,-x*r,1);
}
void init(){
scanf("%d",&n);
for(int i=1;i<=n;++i){
scanf("%d",&a[i])
addSegment(i,i,a[i]-a[i-1]);
}
}
int preSum(int x){
return x*getSum(x,0)-getSum(x,1);
}
int askSum(int l,int r){
return preSum(r)-preSum(l-1);
}
}
权值线段树
权值线段树本质上维护的是序列按值排序后的排名桶。可以支持查询全局第k大(动态插入查询),以及统计逆序对。所以一般要对值进行离散化。对于树上的节点要维护一个区间和sum值;
struct Node{
int l,r,minn,maxn,sum;
}
struct vauleSegmentTree{
int sz;//记录不重复的值的个数
int arrow[N];//存入区间原始数据
Node node[N<<2];
void preSolve(){//预处理,对原序列进行离散化
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",arrow[i]);
sort(arrow+1,arrow+n+1);
sz=unique(arrow+1,arrow+n+1)-arrow-1;
/*去重操作还可以调用系统函数
sz=n>0;
for(int i=2;i<=n;++i){
while(i<=n&&arrow[i]==arrow[sz]) ++i;
if(i<=n) arrow[++sz]=arrow[i];
}*/
}
void build(int s,int l,int r){
node[s].l=l;node[s].r=r;node[s].sum=0;node[s].minn=sz+1;node[s].maxn=0;
if(l==r) return;
int mid=l+r>>1;
build(s<<1,l,mid);build(s<<1|1,mid+1,r);
}
void insert(int s,int x){//插入值
if(node[s].l>x||node[s].r<x) return;
++node[s].sum;
if(node[s].l!=node[s].r){
insert(s<<1,x);insert(s<<1|1,x);
node[s].minn=min(node[s<<1].minn,node[s<<1|1].minn);
node[s].maxn=max(node[s<<1].maxn,node[s<<1|1].maxn);
}
}
int findKVaule(int s,int k){//查询第k大
if(node[s].l==node[s].r) return arrow[node[s].l];
if(node[s<<1].sum<k) return findKVaule(s<<1|1,k-node[s<<1].sum);
return findKVaule(s<<1,k);
}
int lowerBoundFind(int s,int x){
if(node[s].l==node[s].r) return node[s].l;
if(node[s<<1|1].minn>x) return lowerBoundFind(s<<1,x);
return lowerBoundFind(s<<1|1,x);
}//保证有解性,在函数外部要进行有解判定
int upperBoundFind(int s,int x){
if(node[s].l==node[s].r) return node[s].l;
if(node[s<<1].maxn<=x) return upperBoundFind(s<<1|1,x);
return upperBoundFind(s<<1,x);
}//保证有解性,在函数外部要进行有解判定
int lowerBound(int x){
int op=lower_bound(arrow+1,arrow+sz+1,x)-arrow;
if(op==sz+1) return sz;
if(arrow[op]==x) return op;
return op-1;
}
int upperBound(int x){
int op=upper_bound(arrow+1,arrow+sz+1)-arrow;
if(op==sz+1) return 0;
return op;
}
}
主席树
主席树的本质还是权值线段树,但其引入了时刻概念,即每个时刻都有值插入,询问也变成了对某个时刻的询问,所以暴力的想就是将每个时刻建立一棵权值线段树。但这样做的时间复杂度是 O ( n 2 ) O(n^2) O(n2),考虑优化,我们发现每次其实不必要建立整棵树,可以利用之前的节点,所以每次建树,我们只会多开 log n \log n logn个节点,那么总的时间和空间复杂度就是 O ( n log n ) O(n\log n) O(nlogn);主席树支持区间查询第k大;
struct Node{
int l,r,ls,rs,sum;
}
struct ValueSegmentForest{
int sz,cnt;//sz记录值域中不同值的个数,cnt记录节点数
int Rt[N],arrow[N],Arrow[N];//Rt存入每个时刻对应线段树的树根,arrow存原始数据,Arrow存经过处理后的数据
struct Node node[N];
int insert(int pre,int l,int r,int k){
int now=++cnt;
node[now]=node[pre];
node[now].l=l;node[now].r=r;
++node[now].sum;
if(l==r) return now;
int mid=l+r>>1;
if(mid<k) node[now].rs=insert(node[pre].rs,mid+1,r,k);
else node[now].ls=insert(node[pre].ls,l,mid,k);
return now;
}
void preSolve(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",arrow[i]);
memcpy(Arrow,arrow,sizeof arrow);
sort(Arrow+1,Arrow+n+1);
sz=unique(Arrow+1,Arrow+n+1)-Arrow-1;
for(int i=1;i<=n;++i){
int op=lower_bound(Arrow+1,Arrow+sz+1)-Arrow;
Rt[i]=insert(Rt[i-1],1,sz,op);
}
}
int find(int rt,int lt,int k){//rt对应右端点根,lt对应左端点-1的根
if(node[rt].l==node[rt].r) return Arrow[node[rt].r];
int L1=node[rt].ls,L2=node[lt].ls;
int R1=node[rt].rs,R2=node[lt].rs;
if(node[L1].sum-node[L2].sum>=k) return find(L1,L2,k);
return find(R1,R2,k-(node[L1].sum-node[L2].sum));
}
}
树链剖分
树链剖分是对DFS序的一种应用,通过对深度优先搜索的顺序的调整,得到了将树拆分成若干连续时间戳的链的最优拆分,我们便得到了一种在树上的强大的数据结构,支持我们在树上各种各样的操作。而这种调整正是轻重链的拆分。
struct Edge{
int to,pre;
}
struct TreeChainDivision{
int n,root,tim,cnt;//n记录节点总数root记录树根,tim记录时间戳,cnt边计数器
int wson[N],licktop[N],depth[N],siz[N],dfn[N],f[N];//wson记录重儿子,licktop记录树链链头,depth记录节点深度,siz记录子树大小,dfn记录节点时间戳,f记录父节点
int head[N];//邻接链表表头
Edge edge[N];//记录边的信息
int A[N][2];//支持区间修改的树状数组
void lowbit(int x){return x&(-x);}//取低位1
void addPoint(int x,int val,int op){for(;x<=n;x+=lowbit(x)) A[x][op]+=val;}//单点更新
void addSegment(int l,int r,int val){
addPoint(l,val,0);addPoint(l,(l-1)*val,1);
addPoint(r+1,-val,0);addPoint(r+1,-r*val,1);
}
int getSum(int s,int op){
int sum=0;
for(;s>0;s-=lowbit(s)) sum+=A[s][op];
return sum;
}
int preSum(int s){
return s*getSum(s,0)-getSum(s,1);
}
int askSegment(int l,int r){
return preSum(r)-preSum(l-1);
}
void addEdge(int fr,int t){//加边建图
edge[++cnt].to=t;edge[cnt].pre=head[fr];
head[fr]=cnt;
}
void dfs(int now,int fa){//计算子树大小,父节点,重儿子
++siz[now];f[now]=fa;depth[now]=depth[fa]+1;
int maxn=-1;
for(int i=head[now];i;i=edge[i].pre)
if(edge[i].to!=fa){
dfs(edge[i].to,now);
siz[now]+=siz[edge[i].to];
if(maxn<siz[edge[i].to]){
wson[now]=edge[i].to;
maxn=siz[edge[i].to];
}
}
}
void dfs(int now){//建立轻重链时间戳
dfn[now]=++tim;
if(wson[now]) {
licktop[wson[now]]=licktop[now];
dfs(wson[now]);
}
for(int i=head[now];i;i=edge[i].pre)
if(edge[i].to!=f[now]&&edge[i].to!=wson[now])
dfs(edge[i].to);
}
void init(){
int n;
scanf("%d%d",&n,&root);
for(int i=1;i<n;++i){
int a,b;
scanf("%d%d",&a,&b);
addedge(a,b);addedge(b,a);
}
dfs(root,root);dfs(licktop[root]=root);
int val=0;
for(int i=1;i<=n;++i){
int now;
scanf("%d",&now);
addSegement(dfn[i],dfn[i],now-val);
val=now;
}
}
int askLCA(int a,int b){
while(licktop[a]!=licktop[b]){
if(depth[licktop[a]]<depth[licktop[b]])
swap(a,b);
a=f[licktop[a]];
}
return depth[a]<depth[b]?a:b;
}
int askPath(int a,int b){
int val=0;
while(licktop[a]!=licktop[b]){
if(depth[licktop[a]]<depth[licktop[b]])
swap(a,b);
val+=askSegment(dfn[a],dfn[licktop[a]]);
a=f[licktop[a]];
}
if(depth[a]>depth[b]) swap(a,b);
return val+askSegment(dfn[b],dfn[a]);
}
int askSubTree(int s){return askSegmetn(dfn[s],dfn[s]+siz[s]-1);}
void addPath(int a,int b,int val){
while(licktop[a]!=licktop[b]){
if(depth[licktop[a]]<depth[licktop[b]])
swap(a,b);
addSegment(dfn[a],dfn[licktop[a]],val);
a=f[licktop[a]];
}
if(depth[a]>depth[b]) swap(a,b);
addSegment(dfn[b],dfn[a],val);
}
void addSubTree(int s,int val){addSegment(dfn[s],dfn[s]+siz[s]-1,val);}
}