树链剖分

 
前言:似乎好久没写公开的博客了,,,原内容我也是咕咕咕了好久才记得要学的,,,,果然,,人类的本性

这篇博客的操作基于洛谷上的树链剖分模板题:传送门

定义:
  • 重儿子:对于每一个非叶节点,他的儿子A中,以某个儿子B为根,其子树节点最大的那个B就被成为A的重儿子
  • 轻儿子:非叶节点中除了重儿子的其余节点(废话)
  • 重边:连接重儿子及其父亲节点的边
  • 轻边:除了重边以外的边废话*2
  • 重链:
    • 相邻重边链接起来的链
    • 叶子节点自己
      • 每一条重链以轻儿子为起点

其余解释见代码注释

  1 #include<iostream>
  2 #include<cstdio>
  3 using namespace std;
  4 #define ll long long
  5 /*
  6     树链剖分:
  7         个人理解就是给你一颗有点权的树,然后再给你一些骚操作,例如本题的操作
  8         个人认为,全过程就是你先把这个树找到他们的重儿子,然后按照dfs序把他们进行剖分
  9         把这棵树肢解成一段一段的链条,每条链条把他们当成一条线段,再用线段树维护
 10     前置知识:
 11         图论基本常识,带tag的线段树,dfs序
 12 */
 13 namespace the_Death{
 14     const int MAXN=1e5+10;
 15     int n,m,root,mod,tot;
 16     int head[MAXN],ver[MAXN<<1],nxt[MAXN<<1];
 17     void add(int x,int y){
 18         ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;
 19     }//存图
 20     int dfn[MAXN],dfn2[MAXN],size[MAXN],dep[MAXN];
 21     int fa[MAXN],top[MAXN],son[MAXN],dp,id[MAXN];
 22     //树剖所用
 23     //dfn[MAXN]->标记dfs序,dfn2[MAXN]->标记树剖出的链的右端点
 24     //dep[MAXN]->这个点所在的深度,fa[MAXN]->某个点的父亲节点标号
 25     //son[MAXN]->重儿子标号,id[MAXN]->记录某个dfs序的原序号是什么
 26     //size[MAXN]->子树大小,dp->dfs序的序号,top[MAXN]->树剖后某个链的起点
 27     ll w[MAXN],d;int x,y,op;
 28     //w[MAXN]->节点上的初值
 29     //———————————————————————————————————树剖的前置工作————————————————————————————————————————————
 30     void dfs1(int x,int d){
 31         //目的:为了找到重儿子&父亲,某点的深度,以某点为根的子树大小
 32         dep[x]=d;size[x]=1;
 33         //x的深度是d,以x为根的子树大小为1
 34         for(register int i=head[x];i;i=nxt[i]){
 35             int y=ver[i];
 36             if(y==fa[x]) continue;
 37             fa[y]=x;dfs1(y,d+1);size[x]+=size[y];
 38             //标记父节点标号,再向下dfs,而后遍历树后,统计子树大小
 39             if(size[y]>size[son[x]]) son[x]=y;
 40             //标记每个子树的重儿子标号
 41         }
 42     }
 43     void dfs2(int x){
 44         //目的:为了找到dfs序,某条链剖完后的左右端点,某个dfs序所对应的原节点
 45         //关于“先重后轻”处理链,原因见最下面代码结束后的地方
 46         dfn[x]=++dp;id[dp]=x;
 47         //dfn[]->标记dfs序;id[]->序号dp的位置对应x节点
 48         if(!top[x]) top[x]=x;
 49         //如果这个点所在链上没有top[]的话,就指定它是起点
 50         if(son[x]) top[son[x]]=top[x],dfs2(son[x]);
 51         //如果这个点下面有重儿子的话。优先处理重儿子,并且把这个重儿子当为这个链的top[]
 52         for(register int i=head[x];i;i=nxt[i]){
 53             int y=ver[i];
 54             if(y==fa[x]||y==son[x]) continue;
 55             //如果这个点不是重儿子,不是父亲,那就以轻儿子为端点继续搜
 56             dfs2(y);
 57         }
 58         dfn2[x]=dp;//这个链全部剖完后,右端点的dfs序
 59     }
 60     //——————————————————————————————————————线段树————————————————————————————————————————————————
 61     struct ziji{
 62         ll sum,tag;
 63         //tag(i)->以i为根节点的子树应该加上多少
 64         #define sum(i) mn[i].sum
 65         #define tag(i) mn[i].tag
 66     }mn[MAXN<<2];//线段树
 67     void pushup(int x){
 68         sum(x)=(sum(x<<1)+sum(x<<1|1))%mod;
 69     }
 70     void build(int now,int l,int r){
 71         if(l==r){
 72             sum(now)=w[id[l]]%mod;
 73             //由于是把每棵树按照dfs序进行的树剖
 74             //所以每一个叶子节点的值应该是还原dfs序后的真实序号所对应的点权
 75             tag(now)=0;return;
 76         }
 77         tag(now)=0;int mid=l+r>>1;
 78         build(now<<1,l,mid);build(now<<1|1,mid+1,r);
 79         pushup(now);
 80     }
 81     void pushdown(int now,int l,int r){//下放标记
 82         if(!tag(now)) return ;
 83         (tag(now<<1)+=tag(now))%=mod;
 84         (tag(now<<1|1)+=tag(now))%=mod;
 85         //左右子树将要加的值,应该是以自己父亲节点为根要加的值和以自己为根要加的值之和
 86         int mid=l+r>>1;
 87         sum(now<<1)=(sum(now<<1)+tag(now)*(mid-l+1)%mod)%mod;
 88         sum(now<<1|1)=(sum(now<<1|1)+tag(now)*(r-mid)%mod)%mod;
 89         //sum上要加的值,应该就是自己所维护的这一段树链的长度*要以这个点为根所加上的值
 90         //由于add()函数里已经处理了以自己为根的子树加上tag后的值
 91         //所以此时你只要更新自己左右节点的变化值就可以了
 92         tag(now)=0;
 93     }
 94     void add(int now,int l,int r,int L,int R,ll d){
 95         if(L<=l&&r<=R){
 96             (tag(now)+=d)%mod;
 97             sum(now)=(sum(now)+(r-l+1)*d%mod)%mod;
 98             //当你要处理的L,R在你找的范围里,那你直接更新就好了
 99             return ;
100         }
101         pushdown(now,l,r);int mid=l+r>>1;
102         if(L<=mid) add(now<<1,l,mid,L,R,d);
103         if(mid<R) add(now<<1|1,mid+1,r,L,R,d);
104         pushup(now);
105     }
106     ll ask(int now,int l,int r,int L,int R){
107         if(L<=l&&r<=R) return sum(now);
108         pushdown(now,l,r);
109         //如果没有在你找到的区间里,代表这个链的跨度比较大
110         //所以此时你要更新你的tag与sum
111         ll ret=0;int mid=l+r>>1;
112         if(L<=mid) ret=(ret+ask(now<<1,l,mid,L,R))%mod;
113         if(mid<R) ret=(ret+ask(now<<1|1,mid+1,r,L,R))%mod;
114         return ret;
115     }
116     //———————————————————————————————————树链剖分&线段树融合——————————————————————————————————————————
117     //个人认为这一下就是把你剖好的链和你写好的线段树相结合
118     void work1(int x,int y,ll d){
119         //树上加
120         /*
121             原理总括性的说明:
122                 当你要对L到R的规定区间里加d,你所要面对有两种情况:
123                     1.这俩在一条链上->你直接按照线段树的方式进行维护
124                     2.这俩不在一条链上:
125                         首先你要知道,无论你怎么剖分,本质上他们呢还是在树上的。
126                         所以你可以通过类似于倍增找lca的方法,让他们变成一条链上,然后再回到情况1
127                         由于是从下向上更新,所以每次向上走的时候都要对以自己为根的子树进行维护操作
128         */
129         while(top[x]!=top[y]){
130             //如果两点不在一条链上
131             if(dep[top[x]]<dep[top[y]]) swap(x,y);
132             //如果x所在链顶点比y所在链的顶点浅,就交换
133             add(1,1,n,dfn[top[x]],dfn[x],d);
134             x=fa[top[x]];
135             //把x跳到x所在链顶端的那个点的上面一个点
136         }//如此操作,直到x,y在同一个链上
137         if(dep[x]<dep[y]) swap(x,y);
138         add(1,1,n,dfn[y],dfn[x],d);
139         //最后再在这一条链上加
140     }
141     ll work2(int x,int y){//树上查询,同上
142         ll ret=0;
143         while(top[x]!=top[y]){
144             if(dep[top[x]]<dep[top[y]]) swap(x,y);
145             ret=(ret+ask(1,1,n,dfn[top[x]],dfn[x]))%mod;
146             x=fa[top[x]];
147         }
148         if(dep[x]<dep[y]) swap(x,y);
149         ret=(ret+ask(1,1,n,dfn[y],dfn[x]))%mod;
150         return ret;
151     }
152     //——————————————————————————————————————————————主程序————————————————————————————————————————
153     int main(){
154         scanf("%d%d%d%d",&n,&m,&root,&mod);
155         for(register int i=1;i<=n;++i) scanf("%lld",&w[i]);
156         for(register int i=1;i<=n-1;++i)
157             scanf("%d%d",&x,&y),add(x,y),add(y,x);
158         dfs1(root,1);dfs2(root);build(1,1,n);
159         while(m--){
160             scanf("%d",&op);
161             switch(op){
162                 case 1:scanf("%d%d%lld",&x,&y,&d),work1(x,y,d);break;
163                 case 2:scanf("%d%d",&x,&y),printf("%lld\n",work2(x,y));break;
164                 case 3:scanf("%d%lld",&x,&d),add(1,1,n,dfn[x],dfn2[x],d);break;
165                 case 4:scanf("%d",&x),printf("%lld\n",ask(1,1,n,dfn[x],dfn2[x]));break;
166             }
167         }
168         system("pause");return 0;
169     }
170 }
171 int main(void){
172     the_Death::main();
173     return 0;
174 }
View Code
NOTICE:关于为什么第二次dfs要先重后轻

在先重后轻的方式下,你可以保证重儿子的dfs序是连续的,也可以保证子树的编号是来连续的

才不是因为别人都告诉我要先重后轻我才这样做的呢

最后国际惯例:thankyou for your attention

代码参考于 *Miracle*

转载于:https://www.cnblogs.com/fallen-down/p/10976915.html

内容概要:本文深入探讨了Kotlin语言在函数式编程和跨平台开发方面的特性和优势,结合详细的代码案例,展示了Kotlin的核心技巧和应用场景。文章首先介绍了高阶函数和Lambda表达式的使用,解释了它们如何简化集合操作和回调函数处理。接着,详细讲解了Kotlin Multiplatform(KMP)的实现方式,包括共享模块的创建和平台特定模块的配置,展示了如何通过共享业务逻辑代码提高开发效率。最后,文章总结了Kotlin在Android开发、跨平台移动开发、后端开发和Web开发中的应用场景,并展望了其未来发展趋势,指出Kotlin将继续在函数式编程和跨平台开发领域不断完善和发展。; 适合人群:对函数式编程和跨平台开发感兴趣的开发者,尤其是有一定编程基础的Kotlin初学者和中级开发者。; 使用场景及目标:①理解Kotlin中高阶函数和Lambda表达式的使用方法及其在实际开发中的应用场景;②掌握Kotlin Multiplatform的实现方式,能够在多个平台上共享业务逻辑代码,提高开发效率;③了解Kotlin在不同开发领域的应用场景,为选择合适的技术栈提供参考。; 其他说明:本文不仅提供了理论知识,还结合了大量代码案例,帮助读者更好地理解和实践Kotlin的函数式编程特性和跨平台开发能力。建议读者在学习过程中动手实践代码案例,以加深理解和掌握。
内容概要:本文深入探讨了利用历史速度命令(HVC)增强仿射编队机动控制性能的方法。论文提出了HVC在仿射编队控制中的潜在价值,通过全面评估HVC对系统的影响,提出了易于测试的稳定性条件,并给出了延迟参数与跟踪误差关系的显式不等式。研究为两轮差动机器人(TWDRs)群提供了系统的协调编队机动控制方案,并通过9台TWDRs的仿真和实验验证了稳定性和综合性能改进。此外,文中还提供了详细的Python代码实现,涵盖仿射编队控制类、HVC增强、稳定性条件检查以及仿真实验。代码不仅实现了论文的核心思想,还扩展了邻居历史信息利用、动态拓扑优化和自适应控制等性能提升策略,更全面地反映了群体智能协作和性能优化思想。 适用人群:具备一定编程基础,对群体智能、机器人编队控制、时滞系统稳定性分析感兴趣的科研人员和工程师。 使用场景及目标:①理解HVC在仿射编队控制中的应用及其对系统性能的提升;②掌握仿射编队控制的具体实现方法,包括控制器设计、稳定性分析和仿真实验;③学习如何通过引入历史信息(如HVC)来优化群体智能系统的性能;④探索中性型时滞系统的稳定性条件及其在实际系统中的应用。 其他说明:此资源不仅提供了理论分析,还包括完整的Python代码实现,帮助读者从理论到实践全面掌握仿射编队控制技术。代码结构清晰,涵盖了从初始化配置、控制律设计到性能评估的各个环节,并提供了丰富的可视化工具,便于理解和分析系统性能。通过阅读和实践,读者可以深入了解HVC增强仿射编队控制的工作原理及其实际应用效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值