这一篇博客将教你什么?

如何用LCT打延迟标记,LCT和线段树延迟标记间的关系,为什么延迟标记要这样打。

——正片开始——

学习这一篇博客前,确保你会以下知识:

Link-Cut-Tree,普通线段树

当然,不会也没有关系,你可以先收藏这篇博客,等你学了以后再来看。

最好通过了这一道题:【模板】线段树Ⅱ

没有通过也没关系,对于本篇的知识只是一个启发作用。

我们平时使用的Link-Cut-Tree一般只需要打一个翻转标记rev[x]。

然后我们用pushR(x)函数来下发翻转标记。

那么我们现在来看这样一道题:TreeⅡ

练习LCT打标记的绝世好题,可以说就是一道模板题了。

我们来看它需要维护什么:树的权值。

如果能把这个操作2去掉,树剖将绝杀,可惜换不得。

单走一个“树”,nice,直接LCT。

询问权值和,可以,给LCT来一个下传标记,开始你的操作秀。

我们直接来看pushdown部分:

操作有乘和加两种,根据运算法则,乘法优先,所以首先判断

if(lazM[x]!=){...}

然后是加法

if(lazA[x]){...}

最后回到我们的翻转标记。

然后我们来看pushM和pushA部分

#define mul(x,y) x*=y;x%=MOD;
inline void pushM(unsigned int x,unsigned int d){
mul(sum[x],d);mul(val[x],d);//节点信息直接更新
mul(lazM[x],d);mul(lazA[x],d);//按照运算法则先把乘标记乘了,再把加标记乘了
}

有没有回想起什么?没错,就是线段树的懒标记。

我们看看线段树2的懒标记下传部分

void pushdown(int p){
mul(p<<)=(mul(p<<)*mul(p))%P;
mul(p<<|)=(mul(p<<|)*mul(p))%P;
add(p<<)=(add(p<<)*mul(p))%P;
add(p<<|)=(add(p<<|)*mul(p))%P;
sum(p<<)=(sum(p<<)*mul(p))%P;
sum(p<<|)=(sum(p<<|)*mul(p))%P;//按照运算法则更新标记和节点信息
mul(p)=;
add(p<<)=(add(p<<)+add(p))%P;
add(p<<|)=(add(p<<|)+add(p))%P;
sum(p<<)=(sum(p<<)+add(p)*(r(p<<)-l(p<<)+))%P;
sum(p<<|)=(sum(p<<|)+add(p)*(r(p<<|)-l(p<<|)+))%P;//按照运算法则更新标记和节点信息
add(p)=;
}

惊人的一致。

猜到pushA的写法了吧?那我不讲了,后面有代码。

透过现象看本质。

两种数据结构都是在维护一个区间,只不过LCT维护的树上一段路径的区间。

如果这道题把操作2去掉,我们用树链剖分写,线段树维护,一样是这样打标记。

为什么?

如果你当初学线段树的时候理解了线段树2打标记里面先成后加的原理,你可能思考一下就明白了。

这里的原因和线段树非常相似:精度。

我们的标记是打在父节点上的,告诉它它的孩子加了多少,乘了多少。

如果我们先加了那么多,再乘那么多,结果是不一样的,如果非要等价,需要对式子变形。

我们看这样一个式子:(a+b)*c,它并不等价于a*c+b,运算的顺序是会影响结果的。

然而我们打标记的时候并不能确定顺序。这时我们为何不用上很早就明白的运算法则呢?

看下面两种顺序:

先+后*:(a+b)*c = a*c+b*c,先*后+:(a+b)*c = a*c+b

我们发现,先+后*的式子并不等于先*后+的式子,要让它相等必须在加的那一项也*上c。

但是要让先*后+的式子转化成先+后*的式子,我们就必须用到除法,就会变成实数运算,还有可能得到无限小数影响精度。所以我们只需要使用先*后+的优先顺序,并且在打乘法标记时把加法标记也乘上这个值就可以了。

分析完后,相信各位应该能理解数据结构“懒标记”的概念以及为何选择这种优先顺序了。

那么剩下的就是很正常的LCT操作了,给出此题的代码。

注意这道题有一个坑点:模数是51061,看上去很小,然而暗藏出题人的心机。

我们来看51061的平方 ——> 2516125921,再看int的数据范围 —— > 2147483647

于是我们需要开long long,但是我写的时候为了卡常用了unsigned int。

#include<bits/stdc++.h>
using namespace std;
int n,q;
const int N=;
#define MOD 51061
unsigned int fa[N],val[N],ch[N][],rev[N],lazM[N],lazA[N],siz[N],sum[N],stk[N];
#define add(x,y) x+=y;x%=MOD;
#define mul(x,y) x*=y;x%=MOD;
inline unsigned int read(){
unsigned int data=,w=;char ch=;
while(ch!='-' && (ch<''||ch>''))ch=getchar();
if(ch=='-')w=-,ch=getchar();
while(ch>='' && ch<='')data=data*+ch-'',ch=getchar();
return data*w;
}
inline bool chk(unsigned int x){
return ch[fa[x]][]==x;
}
inline bool nroot(unsigned int x){
return ch[fa[x]][]==x||ch[fa[x]][]==x;
}
inline void pushup(unsigned int x){
sum[x]=(sum[ch[x][]]+sum[ch[x][]]+val[x])%MOD;
siz[x]=siz[ch[x][]]+siz[ch[x][]]+;
}
template<class T>inline void fswap(T&x,T&y){
T t=x;x=y;y=t;
}
inline void pushR(unsigned int x){
fswap(ch[x][],ch[x][]);
rev[x]^=;
}
inline void pushM(unsigned int x,unsigned int d){
mul(sum[x],d);mul(val[x],d);
mul(lazM[x],d);mul(lazA[x],d);
}
inline void pushA(unsigned int x,unsigned int d){
add(sum[x],d*siz[x]);add(val[x],d);
add(lazA[x],d);
}
inline void pushdown(unsigned int x){
if(lazM[x]!=){
pushM(ch[x][],lazM[x]);
pushM(ch[x][],lazM[x]);
lazM[x]=;
}
if(lazA[x]){
pushA(ch[x][],lazA[x]);
pushA(ch[x][],lazA[x]);
lazA[x]=;
}
if(rev[x]){
if(ch[x][])pushR(ch[x][]);
if(ch[x][])pushR(ch[x][]);
rev[x]=;
}
}
inline void rotate(unsigned int x){
int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^];
ch[y][k]=w;if(w)fa[w]=y;
if(nroot(y))ch[z][chk(y)]=x;fa[x]=z;
ch[x][k^]=y;fa[y]=x;
pushup(y);pushup(x);
}
inline void splay(unsigned int x){
int y=x,z=;
stk[++z]=y;
while(nroot(y))stk[++z]=y=fa[y];
while(z)pushdown(stk[z--]);
while(nroot(x)){
y=fa[x];z=fa[y];
if(nroot(y)){
if(chk(x)==chk(y))rotate(y);
else rotate(x);
}rotate(x);
}
pushup(x);
}
inline void access(unsigned int x){
for(int y=;x;x=fa[y=x])
splay(x),ch[x][]=y,pushup(x);
}
inline void makeroot(unsigned int x){
access(x);splay(x);
pushR(x);
}
inline int findroot(unsigned int x){
access(x);splay(x);
while(ch[x][])pushdown(x),x=ch[x][];
splay(x);
return x;
}
inline void split(unsigned int x,unsigned int y){
makeroot(x);
access(y);splay(y);
}
inline void link(unsigned int x,unsigned int y){
makeroot(x);
if(findroot(y)!=x)fa[x]=y;
}
inline void cut(unsigned int x,unsigned int y){
makeroot(x);
if(findroot(y)==x && fa[y]==x && !ch[y][]){
fa[y]=ch[x][]=;
pushup(x);
}
}
int main(){
n=read();q=read();
for(int i=;i<=n;i++)val[i]=siz[i]=lazM[i]=;
for(int i=;i<n;i++){
int a=read();int b=read();
link(a,b);
}
char opt[];
int u,v,d;
while(q--){
scanf("%s",opt);
if(opt[]=='+'){
u=read();v=read();d=read();
split(u,v);pushA(v,d);
}else if(opt[]=='-'){
u=read();v=read();
cut(u,v);
u=read();v=read();
link(u,v);
}else if(opt[]=='*'){
u=read();v=read();d=read();
split(u,v);pushM(v,d);
}else{
u=read();v=read();
split(u,v);
printf("%d\n",sum[v]);
}
}
return ;
}

其实这也揭示了数据结构间的联系:形式不同,作用相似。

透过现象看本质,通过结果推原因,都是学习数据结构的重要方式。

数据结构不只是背背代码,用来加速这么简单的,如果明白了数据结构的运行方式和原理,

你也一定会感慨里面蕴含着的发明者的智慧和它给你带来的知识上的进步。

我是灯塔...一个喜欢数据结构的OIer博主,关注我,我将给你带来更多精彩的文章。

[线段树系列] LCT打延迟标记的正确姿势的更多相关文章

  1. 【POJ】3468 A Simple Problem with Integers ——线段树 成段更新 懒惰标记

    A Simple Problem with Integers Time Limit:5000MS   Memory Limit:131072K Case Time Limit:2000MS Descr ...

  2. POJ 3468 线段树 成段更新 懒惰标记

    A Simple Problem with Integers Time Limit:5000MS   Memory Limit:131072K Case Time Limit:2000MS Descr ...

  3. 题解报告:poj 3468 A Simple Problem with Integers(线段树区间修改+lazy懒标记or树状数组)

    Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...

  4. 题解报告:hdu 1698 Just a Hook(线段树区间修改+lazy懒标记的运用)

    Problem Description In the game of DotA, Pudge’s meat hook is actually the most horrible thing for m ...

  5. BZOJ2001 HNOI2010城市建设(线段树分治+LCT)

    一个很显然的思路是把边按时间段拆开线段树分治一下,用lct维护MST.理论上复杂度是O((M+Q)logNlogQ),实际常数爆炸T成狗.正解写不动了. #include<iostream> ...

  6. bzoj 4025 二分图——线段树分治+LCT

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=4025 线段树分治,用 LCT 维护链的长度即可.不过很慢. 正常(更快)的方法应该是线段树分 ...

  7. SPOJ GSS3 线段树系列1

    SPOJ GSS系列真是有毒啊! 立志刷完,把线段树搞完! 来自lydrainbowcat线段树上的一道例题.(所以解法参考了lyd老师) 题意翻译 n 个数, q 次操作 操作0 x y把 Ax 修 ...

  8. 【洛谷P4319】 变化的道路 线段树分治+LCT

    最近学了一下线段树分治,感觉还蛮好用... 如果正常动态维护最大生成树的话用 LCT 就行,但是这里还有时间这一维的限制. 所以,我们就把每条边放到以时间为轴的线段树的节点上,然后写一个可撤销 LCT ...

  9. P3206 [HNOI2010]城市建设 [线段树分治+LCT维护动态MST]

    Problem 这题呢 就边权会在某一时刻变掉-众所周知LCT不支持删边的qwq- 所以考虑线段树分治- 直接码一发 如果 R+1 这个时间修改 那就当做 [L,R] 插入了一条边- 然后删的边和加的 ...

随机推荐

  1. JQuery学习笔记之手网琴效果

    这种东西在网上多的是,最近在学JQuery,所以就写了个随笔 <!DOCTYPE html> <html lang="en"> <head> & ...

  2. 第六届蓝桥杯java b组第8题

    乐羊羊饮料厂正在举办一次促销优惠活动.乐羊羊C型饮料,凭3个瓶盖可以再换一瓶C型饮料,并且可以一直循环下去,但不允许赊账. 请你计算一下,如果小明不浪费瓶盖,尽量地参加活动,那么,对于他初始买入的n瓶 ...

  3. Jmeter not found in class'org.json.JSONObject 问题

    前景:公司有银行的项目要进行压测,但是接口有近视RSA加密,需发送签名,只能使用java编写原生接口脚本打包成jar使用BeanShell Sampler去调用发送请求.在使用的过程中遇到了如下问题. ...

  4. SpringBootSecurity学习(05)网页版登录内存中配置默认用户

    默认用户 前面的例子中我们使用的都是配置文件中配置好的默认用户: 除了可以配置账号密码,还可以在配置文件中配置角色: 这个角色是后面实现权限过滤的重要内容,后面会重点讨论. 在内存中配置默认用户 这样 ...

  5. SpringBoot进阶教程(六十二)整合Kafka

    在上一篇文章<Linux安装Kafka>中,已经介绍了如何在Linux安装Kafka,以及Kafka的启动/关闭和创建发话题并产生消息和消费消息.这篇文章就介绍介绍SpringBoot整合 ...

  6. 阿里云虚拟主机安装wordpress,提示连接数据库失败的解决方法

      很多新手在购买的虚拟主机后就开始尝试安装,却发现连接数据库老是出错,不知道什么问题,反复检查了自己填写的数据库连接信息发现也没有问题,这个时候我们似乎就没法了. 但这个其实是后台空间的设置问题,你 ...

  7. Jetpack系列:Paging组件帮你解决分页加载实现的痛苦

    相信很多小伙伴们在项目实战中,经常会用到界面的分页显示.加载更多等功能.需要针对具体功能做针对性开发和调试,耗时耗力. Paging组件的使用将这部分的工作简化,从而让开发者更专注于业务的具体实现.下 ...

  8. Zookeeper 学习笔记之 节点个数

    zookeeper的节点配置的个数推荐是奇数个这是为什么呢? 选举机制 两种情况无法选出leader: 整个集群只有2台服务器(注意不是只剩2台,而是集群的总节点数为2) 整个集群超过半数机器挂掉. ...

  9. java第4天:String static Arrays类,Math类

    1 字符串的概述和特点 字符串一旦创建,是不可变的. 有双引号的就是字符串 *** 2 字符串的三种构造方法 2-1 第一种: 格式:String str = new String();| :-| 2 ...

  10. 在线影视平台人人影视 v3.2.1 绿色便携版

    人人影视是一款可以方便观看美剧和国外大片的视频播放软件,支持在线观看.网盘转存.离线缓存.所有客户端离线下载均加密传输,不用担心任何安全问题.全程加密的 P2P 传输,让热门资源下载更快,海外党不再惧 ...