。。。。

有点懒;


需要先理解几个概念:

1. LCA

2. 线段树(熟练,要不代码能调一天)

3. 图论的基本知识(dfs序的性质)

这大概就好了;

定义

  1.重儿子:一个点所连点树size最大的,这个son被称为这个点的重儿子;

  2.轻儿子:一个点所连点除重儿子以外的都是轻儿子;

  3.重链:从一个轻儿子或根节点开始沿重儿子走所成的链;

步骤

  在代码里,结合代码更清晰。。。(其实是太懒了)

 有重点需要注意的东西在code中有提到,仔细看。。。。

#include<bits/stdc++.h>
#define maxn 100007
#define le(x) x<<1
#define re(x) x<<1|1
using namespace std;
int n,m,root,mod,a[maxn],head[maxn],fa[maxn],son[maxn],cnt,tag[maxn<<2];
//a:原始点值,fa:父亲节点,son:重儿子,tag:懒标记
int top[maxn],sz[maxn],id[maxn],dep[maxn],w[maxn],cent,tr[maxn<<2];
//top:所在重链的头结点,sz:子树大小,id:dfs序,dep:深度
//w:dfs序所对应的值(建线段树),tr:线段树
struct node{
int next,to;
}edge[maxn<<2]; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9') f=s=='-'?-1:1,s=getchar();
while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+s-'0',s=getchar();
x*=f;
} inline void add(int u,int v){
edge[++cent]=(node){head[u],v};head[u]=cent;
}
//-----------------------------------------------------线段树红色预警
void push_up(int p){
tr[p]=tr[le(p)]+tr[re(p)];
tr[p]%=mod;
} void build(int l,int r,int p){
if(l==r){
tr[p]=w[l];
return ;
}
int mid=(l+r)>>1;
build(l,mid,le(p));
build(mid+1,r,re(p));
push_up(p);
} void push_down(int l,int r,int p,int k){
int mid=l+r>>1;
tr[le(p)]+=k*(mid-l+1),tr[re(p)]+=k*(r-mid);
tr[le(p)]%=mod,tr[re(p)]%=mod;
tag[le(p)]+=k,tag[re(p)]+=k;
tag[le(p)]%=mod,tag[re(p)]%=mod;
} void r_add(int nl,int nr,int l,int r,int p,int k){
if(nl<=l&&nr>=r){
tr[p]+=k*(r-l+1);tag[p]+=k;
tr[p]%=mod,tag[p]%=mod;
return ;
}
push_down(l,r,p,tag[p]),tag[p]=0;
int mid=(l+r)>>1;
if(nl<=mid) r_add(nl,nr,l,mid,le(p),k);
if(nr>mid) r_add(nl,nr,mid+1,r,re(p),k);
push_up(p);
} int r_query(int nl,int nr,int l,int r,int p){
int ans=0;
if(nl<=l&&nr>=r) return tr[p];
push_down(l,r,p,tag[p]),tag[p]=0;
int mid=l+r>>1;
if(nl<=mid) ans+=r_query(nl,nr,l,mid,le(p)),ans%=mod;
if(nr>mid) ans+=r_query(nl,nr,mid+1,r,re(p)),ans%=mod;
push_up(p);
return ans;
} //-----------------------------------------------------线段树结束
//-----------------------------------------------------开始预处理 void dfs1(int x){
sz[x]=1;//sz初始化
int max_part=-1;//max_part更新寻找重儿子
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]) continue;
fa[y]=x,dep[y]+=dep[x]+1;//更新子节点,准备开始继续dfs1
dfs1(y);sz[x]+=sz[y];//更新自身的sz数组
if(max_part<sz[y]) son[x]=y,max_part=sz[y];//更新重儿子
}
}
/*dfs1功能介绍
1.更新fa数组;
2.更新dep数组;
3.更新sz数组;
4.更新son数组;
*/ void dfs2(int x,int t){
id[x]=++cnt,w[cnt]=a[x],top[x]=t;//更新dfs序,dfs序所对的值,重链头节点
if(!son[x]) return ;
dfs2(son[x],t);
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]||y==son[x]) continue;
dfs2(y,y);
}
}
/*dfs2功能介绍
1.更新id数组;
2.更新w数组;
3.更新top数组
*/ //------------------------------------------------预处理结束
//------------------------------------------------开始主要操作 //其实没有说的这么简单,这里重点是理解重链之间的跳跃方式,线段树的优化
//一个性质:重链上的dfs序是连续的,dfs1在dfs2前的原因就在此 int road_query(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);//从最下面往上跳
ans+=r_query(id[top[x]],id[x],1,n,1);//更新重链
ans%=mod;
x=fa[top[x]];//跳到重链头的fa
}
if(dep[x]>dep[y]) swap(x,y);
ans+=r_query(id[x],id[y],1,n,1);//已经在同一条重链上,直接加
return ans%mod;
} int tree_query(int x){
return r_query(id[x],id[x]+sz[x]-1,1,n,1)%mod;
}//一个性质:在同一颗子树上的dfs序是连续的 void road_add(int x,int y,int k){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
r_add(id[top[x]],id[x],1,n,1,k);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
r_add(id[x],id[y],1,n,1,k);
return ;
}//类比 void tree_add(int x,int k){
r_add(id[x],id[x]+sz[x]-1,1,n,1,k);
return ;
}//相同的性质 //-----------------------------------------------树链剖分 int main(){
scan(n),scan(m),scan(root),scan(mod);
for(int i=1;i<=n;i++) scan(a[i]);
for(int i=1,u,v;i<=n-1;i++)
scan(u),scan(v),add(u,v),add(v,u);
dfs1(root),dfs2(root,root),build(1,n,1);
for(int i=1;i<=m;i++){
int type,x,y,z;
scan(type);
if(type==1) scan(x),scan(y),scan(z),
road_add(x,y,z);
else if(type==2) scan(x),scan(y),
printf("%d\n",road_query(x,y));
else if(type==3) scan(x),scan(z),
tree_add(x,z);
else if(type==4) scan(x),
printf("%d\n",tree_query(x));
}
return 0;
}

好了,可以开始调代码了

拓展:

  树链剖分,作为一个优秀的暴力结构,以O(n logn logn)的时间复杂度完成路径查询,在子树查询做到了nlogn级别,所以不得不说其优秀;

  但是,它的作用远不及此:

  1.LCA查询:

    与倍增相同,树链剖分可以用logn的时间复杂度完成LCA查询(跳跃性好像更优),而他的初始化是两遍dfs O(n),理论上更优。

    可以猜测,LCA依旧运用重链跳法,然后比较即可,这里给出示范代码

int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]>dep[y]?y:x;
}//只要看懂树链剖分的基本操作,这个很简单

    可以看到,其实代码很短。。。

  2.换根操作:

    设现在的根是root,我们可以发现,换根对于路径上的操作并没有影响,但是子树操作就会影响了,所以我们分类讨论

      设u为我们要查的子树的根节点

      (1)如果root=u,那么子树即为整棵树;

      (2)设 lca 为root和u的LCA,这里可以用上面所讲的树链剖分做,如果lca!=u,那么root并不是u的子节点,所以对于查询并不影响,常规操作即可

      (3)如果lca=u,那么u节点的子树就是整颗树减去u-root这个路径上与u相挨的节点v的子树即可,这里给出logn求点v的方法

//前提条件:要求的节点相挨的节点u,必须是root的LCA
int find(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);//从最下往上跳
if(fa[top[x]]==y) return top[x];//如果y是x所在重链top的父亲节点,那么就可以返回了
x=fa[top[x]];//跳
}
if(dep[x]<dep[y]) swap(x,y);//让y最浅
return son[y];// 因为在一条重链上,那么重儿子一定是路径上与要求节点相挨的
}

    整个操作的代码层次感我写的还是比较清楚了

void tree_add(int x,int k){
if(root==x) r_add(1,n,1,n,1,k);//CASE 1
else{
int lca=Lca(x,root);
if(lca!=x) r_add(id[x],id[x]+sz[x]-1,1,n,1,k);//CASE 2
else{
int dson=find(x,root);
r_add(1,n,1,n,1,k);
r_add(id[dson],id[dson]+sz[dson]-1,1,n,1,-k);
}//CASE 3
}
return ;
} ll tree_query(int x){
if(root==x) return r_query(1,n,1,n,1);//CASE 1
else{
int lca=Lca(x,root);
if(lca!=x) return r_query(id[x],id[x]+sz[x]-1,1,n,1);//CASE 2
else{
int dson=find(x,root);
return r_query(1,n,1,n,1)-r_query(id[dson],id[dson]+sz[dson]-1,1,n,1);
}//CASE 3
}
}

推荐评测网站LOJ。。。(因为洛谷没有换根操作)

AC代码附上

#include<bits/stdc++.h>
#define maxn 100007
#define ol putchar('\n')
#define le(x) x<<1
#define re(x) x<<1|1
#define ll long long
using namespace std;
int n,m,head[maxn],cent,dep[maxn],son[maxn],fa[maxn],vis[maxn];
int top[maxn],a[maxn],id[maxn],w[maxn],sz[maxn],cnt,ij,root;
ll tr[maxn<<3],tag[maxn<<3];
struct node{
int next,to;
}edge[maxn<<3]; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9') f=s=='-'?-1:1,s=getchar();
while(s>='0'&&s<='9') x=(x<<3)+(x<<1)+s-'0',s=getchar();
x*=f;
}
template<typename type_of_print>
inline void print(type_of_print x){
if(x<0) putchar('-'),x=-x;
if(x>9) print(x/10);
putchar(x%10+'0');
} inline void add(int u,int v){
edge[++cent]=(node){head[u],v};head[u]=cent;
} void push_up(int p){
tr[p]=tr[le(p)]+tr[re(p)];
} void push_down(int l,int r,int p,ll k){
int mid=l+r>>1;
tr[le(p)]+=1ll*(mid-l+1)*k,
tr[re(p)]+=1ll*(r-mid)*k,
tag[le(p)]+=k,tag[re(p)]+=k;
} void build(int l,int r,int p){
if(l==r){
tr[p]=w[l];
return ;
}
int mid=l+r>>1;
build(l,mid,le(p));
build(mid+1,r,re(p));
push_up(p);
} void r_add(int nl,int nr,int l,int r,int p,int k){
if(nl<=l&&nr>=r){
tr[p]+=1ll*(r-l+1)*k;
tag[p]+=1ll*k;
return ;
}
push_down(l,r,p,tag[p]),tag[p]=0;
int mid=l+r>>1;
if(nl<=mid) r_add(nl,nr,l,mid,le(p),k);
if(nr>mid) r_add(nl,nr,mid+1,r,re(p),k);
push_up(p);
} ll r_query(int nl,int nr,int l,int r,int p){
ll ans=0;
if(nl<=l&&nr>=r) return tr[p];
push_down(l,r,p,tag[p]),tag[p]=0;
int mid=l+r>>1;
if(nl<=mid) ans+=r_query(nl,nr,l,mid,le(p));
if(nr>mid) ans+=r_query(nl,nr,mid+1,r,re(p));
return ans;
} void dfs1(int x){
sz[x]=1;int max_part=-1;vis[x]++;
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa[x]) continue;
fa[y]=x;dep[y]=dep[x]+1;
dfs1(y);sz[x]+=sz[y];
if(max_part<sz[y]) son[x]=y,max_part=sz[y];
}
} void dfs2(int x,int t){
id[x]=++cnt;w[cnt]=a[x];top[x]=t;
if(!son[x]) return ;
dfs2(son[x],t);
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==son[x]||fa[x]==y) continue;
dfs2(y,y);
}
} int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
return dep[x]>dep[y]?y:x;
}//只要看懂树链剖分的基本操作,这个很简单 //前提条件:要求的节点相挨的节点u,必须是root的LCA
int find(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);//从最下往上跳
if(fa[top[x]]==y) return top[x];//如果y是x所在重链top的父亲节点,那么就可以返回了
x=fa[top[x]];//跳
}
if(dep[x]<dep[y]) swap(x,y);//让y最浅
return son[y];// 因为在一条重链上,那么重儿子一定是路径上与要求节点相挨的
} void tree_add(int x,int k){
if(root==x) r_add(1,n,1,n,1,k);//CASE 1
else{
int lca=Lca(x,root);
if(lca!=x) r_add(id[x],id[x]+sz[x]-1,1,n,1,k);//CASE 2
else{
int dson=find(x,root);
r_add(1,n,1,n,1,k);
r_add(id[dson],id[dson]+sz[dson]-1,1,n,1,-k);
}//CASE 3
}
return ;
} ll tree_query(int x){
if(root==x) return r_query(1,n,1,n,1);//CASE 1
else{
int lca=Lca(x,root);
if(lca!=x) return r_query(id[x],id[x]+sz[x]-1,1,n,1);//CASE 2
else{
int dson=find(x,root);
return r_query(1,n,1,n,1)-r_query(id[dson],id[dson]+sz[dson]-1,1,n,1);
}//CASE 3
}
} void road_add(int x,int y,ll k){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
r_add(id[top[x]],id[x],1,n,1,k);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
r_add(id[x],id[y],1,n,1,k);
return ;
} ll road_query(int x,int y){
ll ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]]) swap(x,y);
ans+=r_query(id[top[x]],id[x],1,n,1);
x=fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
ans+=r_query(id[x],id[y],1,n,1);
return ans;
} int main(){
// freopen("cin.in","r",stdin);
// freopen("co.out","w",stdout);
scan(n);
for(int i=1;i<=n;i++) scan(a[i]);
for(int i=2,v;i<=n;i++) scan(v),add(i,v),add(v,i);
dfs1(1),dfs2(1,1),build(1,n,1);root=1;
scan(m);
for(int i=1;i<=m;i++){
int type,x,y,z;
scan(type),scan(x);
if(type==1) root=x;
else if(type==2) scan(y),scan(z),road_add(x,y,z);
else if(type==3) scan(z),tree_add(x,z);
else if(type==4) scan(y),printf("%lld\n",road_query(x,y));
else if(type==5) printf("%lld\n",tree_query(x));
}
return 0;
}

树链剖分(附带LCA和换根)——基于dfs序的树上优化的更多相关文章

  1. BZOJ3159决战——树链剖分+非旋转treap(平衡树动态维护dfs序)

    题目描述 输入 第一行有三个整数N.M和R,分别表示树的节点数.指令和询问总数,以及X国的据点. 接下来N-1行,每行两个整数X和Y,表示Katharon国的一条道路. 接下来M行,每行描述一个指令或 ...

  2. 树链剖分求LCA

    树链剖分中各种数组的作用: siz[]数组,用来保存以x为根的子树节点个数 top[]数组,用来保存当前节点的所在链的顶端节点 son[]数组,用来保存重儿子 dep[]数组,用来保存当前节点的深度 ...

  3. Luogu 2680 NOIP 2015 运输计划(树链剖分,LCA,树状数组,树的重心,二分,差分)

    Luogu 2680 NOIP 2015 运输计划(树链剖分,LCA,树状数组,树的重心,二分,差分) Description L 国有 n 个星球,还有 n-1 条双向航道,每条航道建立在两个星球之 ...

  4. Luogu 2590 [ZJOI2008]树的统计 / HYSBZ 1036 [ZJOI2008]树的统计Count (树链剖分,LCA,线段树)

    Luogu 2590 [ZJOI2008]树的统计 / HYSBZ 1036 [ZJOI2008]树的统计Count (树链剖分,LCA,线段树) Description 一棵树上有n个节点,编号分别 ...

  5. cogs 2450. 距离 树链剖分求LCA最近公共祖先 快速求树上两点距离 详细讲解 带注释!

    2450. 距离 ★★   输入文件:distance.in   输出文件:distance.out   简单对比时间限制:1 s   内存限制:256 MB [题目描述] 在一个村子里有N个房子,一 ...

  6. BZOJ 2243: [SDOI2011]染色 树链剖分 倍增lca 线段树

    2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/pr ...

  7. cogs 2109. [NOIP 2015] 运输计划 提高组Day2T3 树链剖分求LCA 二分答案 差分

    2109. [NOIP 2015] 运输计划 ★★★☆   输入文件:transport.in   输出文件:transport.out   简单对比时间限制:3 s   内存限制:256 MB [题 ...

  8. HDU2586 How far away ? (树链剖分求LCA)

    用树链剖分求LCA的模板: 1 #include<iostream> 2 #include<algorithm> 3 using namespace std; 4 const ...

  9. 【树链剖分】洛谷P3379 树链剖分求LCA

    题目描述 如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先. 输入输出格式 输入格式: 第一行包含三个正整数N.M.S,分别表示树的结点个数.询问的个数和树根结点的序号. 接下来N-1行每 ...

随机推荐

  1. 服务器报错"您的主机中的软件中止了一个已建立的连接"

    网上很多的说法都模棱两可,只是说和远程连接有关,这个说的太泛泛了. 我现在遇到的问题是java web出现的, 执行表单提交的时候出现该错误,原因是ajax和表单同时提交导致的, 相信很多朋友用了aj ...

  2. 【Flutter】可滚动组件之CustomScrollView

    前言 CustomScrollView是可以使用Sliver来自定义滚动模型(效果)的组件.它可以包含多种滚动模型,举个例子,假设有一个页面,顶部需要一个GridView,底部需要一个ListView ...

  3. 来不及解释!Linux常用命令大全,先收藏再说

    摘要:Linux常用命令,很适合你的. 一提到操作系统,我们首先想到的就是windows和Linux.Windows以直观的可视化的方式操作,特别适合在桌面端PC上操作执行相应的软件.相比较Windo ...

  4. python_字典(dict)

    dict 一.结构: info = { "key":"value", "key":"value" } print(inf ...

  5. SpringSecurity应用篇

    前面吹水原理吹了一篇幅了,现在讲解下应用篇幅,前面说过,如果要用SpringSecurity的话要先导入一个包 <dependency> <groupId>org.spring ...

  6. [APUE] 文件 I/O

    文件操作相关 API:open, read, write, lseek, close. 多进程共享文件的相关 API:dup, dup2, fcntl, sync, fsync, ioctl. 文件操 ...

  7. ALV中的分隔条(SPLITTER_CONTROL)

    如上图,可以做成左右的分割,当然也可以做成上下的分割效果,在每个分割的容器内,显示各自的内容. 需要使用的class: cl_gui_splitter_container, cl_gui_custom ...

  8. 2.4V升3.3V,2.4V升3V,1A大电流升压芯片

    两节镍氢电池串联就是1.2V+1.2V=2.4V的供电电压了,2.4V升3V, 2.4V升3.3V的话,就能稳压稳定给模块供电了,镍氢电池是会随着使用的电池电量减少的话,电池的电压也是跟着变化的,导致 ...

  9. 面试常问的ArrayQueue底层实现

    public class ArrayQueue<T> extends AbstractList<T>{ //定义必要的属性,容量.数组.头指针.尾指针 private int ...

  10. Markdown里常用的HTML元素

    转义:\ 换行:<br/> 红色文字:<font color=#FF0000>字体改成红色了</font> A标签 新窗口:<a href="xxx ...