SGTtrick
SGTtrick
By 蒟蒻 ldxoiBy\ 蒟蒻\ ldxoiBy 蒟蒻 ldxoi
Chapter 1.关于线段树操作的一些分析
我们知道,线段树有两个核心的函数pushdownpushdownpushdown和pushuppushuppushup。
以及两类对于一段区间进行操作的函数updateupdateupdate和queryqueryquery
博主在这里简单讲一下几个函数的功能:
首先我们假设用ValValVal表示维护信息的类型,TagTagTag表示懒标记的类型。
显然一个线段树节点(类型为NodeNodeNode)是由一个ValValVal元素和一个TagTagTag以及表示区间管辖范围的l,rl,rl,r构成的。
struct Tag{...};
struct Val{...};
struct Node{Val val;Tag tag;int l,r;}T[N<<2];
- pushdown(int p)pushdown(int\ p)pushdown(int p)表示将ppp的标记下传给ppp的儿子并更新它们的信息和标记
- pushup(int p)pushup(int\ p)pushup(int p)表示把ppp儿子的信息合并成ppp的信息
- query(int p,int ql,int qr)query(int\ p,int\ ql,int\ qr)query(int p,int ql,int qr)表示查询区间[ql,qr][ql,qr][ql,qr]的一些信息
- update(int p,int ql,int qr,Tag v)update(int\ p,int\ ql,int\ qr,Tag\ v)update(int p,int ql,int qr,Tag v)表示用标记vvv对区间[ql,qr][ql,qr][ql,qr]的信息和标记进行更新
可以看出来这些函数分成两类:
- 父亲朝儿子转移:update,pushdownupdate,pushdownupdate,pushdown
- 儿子朝父亲转移:query,pushupquery,pushupquery,pushup
而我们继续将这几个函数的功能进行拆分并取上并集(雾,发现它其实是这几个东西的组合:
- 两个ValValVal类型的元素的合并
- 一个TagTagTag类型的元素对一个ValValVal类型的元素的更新
- 一个TagTagTag类型的元素对一个TagTagTag类型的元素的更新
inline Tag operator+(const Tag&a,const Tag&b){...}
inline void operator+=(Tag&a,const Tag&b){a=a+b;}
inline Val operator+(const Val&a,const Tag&b){...}
inline void operator+=(Val&a,const Tag&b){a=a+b}
inline Val operator+(const Val&a,const Val&b){...}
inline void operator+=(Val&a,const Val&b){a=a+b;}
如果我们对这几个操作重载运算符的话,那么上述函数的实现就很简单了,为了让实现更加简便可以设计一个pushnow函数pushnow函数pushnow函数。
- pushuppushuppushup函数:把儿子的ValValVal合并成自己的ValValVal。
inline void pushup(int p){
T[p].val=T[lc].val+T[rc].val;
}
- pushnowpushnowpushnow函数:pushnow(int p,Tag v)pushnow(int\ p,Tag\ v)pushnow(int p,Tag v)表示用标记vvv更新节点ppp的信息和标记。
代码:
inline void pushnow(int p,Tag v){
T[p].val+=v,T[p].tag+=v;
}
- pushdownpushdownpushdown函数:用自己的TagTagTag去更新儿子的ValValVal和TagTagTag
inline void pushdown(int p){
if(check(T[p].tag))return;
pushnow(lc,T[p].tag),pushnow(rc,T[p].tag);
T[p].tag=tag_empty;
}
- queryqueryquery函数:把要查询的区间拆成线段树上至多logloglog个区间把它们的ValValVal按一定顺序合并起来。
inline Val query(int p,int ql,int qr){
if(ql>T[p].r||qr<T[p].l)return val_empty;
if(ql<=T[p].l&&T[p].r<=qr)return T[p].val;
pushdown(p);
if(qr<=mid)return query(lc,ql,qr);
if(qr>mid)return query(rc,ql,qr);
return query(lc,ql,qr)+query(rc,ql,qr);
}
- updateupdateupdate函数:把要修改的区间拆成线段树上至多logloglog个区间分别用给出的TagTagTag更新它们的TagTagTag和ValValVal。
inline void update(int p,int ql,int qr,Tag v){
if(ql>T[p].r||qr<T[p].l)return;
if(ql<=T[p].l&&T[p].r<=qr)return pushnow(p,v);
pushdown(p);
if(qr<=mid)update(lc,ql,qr,v);
else if(ql>mid)update(rc,ql,qr,v);
else update(lc,ql,qr,v),update(rc,ql,qr,v);
pushup(p);
}
所以当你拿到一道线段树题的时候,思考上述三种运算符如何重载即可qwqqwqqwq。
那么最后我们给上一波上述所有内容合起来的伪代码:才不会告诉你这是一个通用的框架呢
namespace SGT{
#define lc (p<<1)
#define rc (p<<1|1)
#define mid (T[p].l+T[p].r>>1)
struct Tag{...};
struct Val{...};
const Tag tag_empty=(Tag){...};
const Val val_empty=(Val){...};
inline Tag operator+(const Tag&a,const Tag&b){...}
inline void operator+=(Tag&a,const Tag&b){a=a+b;}
inline Val operator+(const Val&a,const Tag&b){...}
inline void operator+=(Val&a,const Tag&b){a=a+b}
inline Val operator+(const Val&a,const Val&b){...}
inline void operator+=(Val&a,const Val&b){a=a+b;}
struct Node{Val val;Tag tag;int l,r;}T[N<<2];
inline bool check(const Tag&v){...}
inline void pushdown(int p){
if(check(T[p].tag))return;
T[lc].val+=T[p].tag,T[lc].tag+=T[p].tag;
T[rc].val+=T[p].tag,T[rc].tag+=T[p].tag;
T[p].tag=tag_empty;
}
inline void pushup(int p){
T[p].val=T[lc].val+T[rc].val;
}
inline void build(int p,int l,int r){
T[p]=(Node){val_empty,tag_empty,l,r};
if(l==r){
T[p].val=(Val){...};
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
pushup(p);
}
inline void update(int p,int ql,int qr,Tag v){
if(ql>T[p].r||qr<T[p].l)return;
if(ql<=T[p].l&&T[p].r<=qr){
T[p].val+=v,T[p].tag+=v;
return;
}
pushdown(p);
if(qr<=mid)update(lc,ql,qr,v);
else if(ql>mid)update(rc,ql,qr,v);
else update(lc,ql,qr,v),update(rc,ql,qr,v);
pushup(p);
}
inline Val query(int p,int ql,int qr){
if(ql>T[p].r||qr<T[p].l)return val_empty;
if(ql<=T[p].l&&T[p].r<=qr)return T[p].val;
pushdown(p);
if(qr<=mid)return query(lc,ql,qr);
if(qr>mid)return query(rc,ql,qr);
return query(lc,ql,qr)+query(rc,ql,qr);
}
#undef lc
#undef rc
#undef mid
}
Chapter 2.线段树的两种写法
吐槽:我吐我自己
这里简单谈一谈线段树的两种实现方法:dfsdfsdfs版和bfsbfsbfs版,其中后者为我瞎yyyyyy的,经过测试实际效果跟dfsdfsdfs版差距不大。
我才不会告诉你们我测出来很多题用第二种写法要慢一些呢
好吧我承认我发现的这种bfsbfsbfs版写法很鸡肋。
正题
众所周知,搜索有几种顺序,其中有两种很著名分别叫做dfsdfsdfs和bfsbfsbfs。
而我们的常规线段树就是使用的dfsdfsdfs序。
然而在博主的努力尝试下,bfsbfsbfs序也是可以处理的。
为什么呢?
因为在Chapter 1Chapter\ 1Chapter 1中我们已经谈到过线段树的queryqueryquery和updateupdateupdate操作的本质了:
- queryqueryquery函数:把要查询的区间拆成线段树上至多logloglog个区间把它们的ValValVal按顺序合并起来。
- updateupdateupdate函数:把要修改的区间拆成线段树上至多logloglog个区间分别用给出的TagTagTag更新它们的TagTagTag和ValValVal。
这样的话,只要按照顺序把这logloglog段区间找到再合起来一定就可以维护所有的操作。
因为按照bfsbfsbfs版本的顺序来合并也是正确的。
如何维护顺序?用一个队列即可。
于是我们可以给出bfsbfsbfs版的一些框架,这里跟dfsdfsdfs版本相同的函数就不拿上来了。
代码如下:
namespace SGT{
...
int q[N<<2],hd,tl;
inline void build(int n){
q[hd=tl=1]=1,T[1]=(Node){val_empty,tag_empty,1,n};
while(hd<=tl){
int p=q[hd++];
if(T[p].l==T[p].r){
T[p].val=(Val){...};
continue;
}
T[lc]=(Node){val_empty,tag_empty,T[p].l,mid};
T[rc]=(Node){val_empty,tag_empty,mid+1,T[p].r};
q[++tl]=lc,q[++tl]=rc;
}
for(ri i=tl;i^1;--i)T[q[i]>>1].val+=T[q[i]].val;
}
inline void update(int p,int ql,int qr,Tag v){
q[hd=tl=1]=1;
while(hd<=tl){
int p=q[hd++];
if(ql>T[p].r||qr<T[p].l)continue;
if(ql<=T[p].l&&T[p].r<=qr){
pushnow(p,v);
continue;
}
pushdown(p);
if(qr<=mid)q[++tl]=lc,T[p].val=T[rc].val;
else if(ql>mid)q[++tl]=rc,T[p].val=T[lc].val;
else q[++tl]=lc,q[++tl]=rc,T[p].val=val_empty;
}
for(ri i=tl;i^1;--i)T[q[i]>>1].val+=T[q[i]].val;
}
inline Val update(int p,int ql,int qr,Tag v){
Val ret=val_empty;
q[hd=tl=1]=1;
while(hd<=tl){
int p=q[hd++];
if(ql>T[p].r||qr<T[p].l)continue;
if(ql<=T[p].l&&T[p].r<=qr){
ret+=T[p].val;
continue;
}
pushdown(p);
if(qr<=mid)q[++tl]=lc;
else if(ql>mid)q[++tl]=rc;
else q[++tl]=lc,q[++tl]=rc;
}
return ret;
}
...
}
经过我们的努力改动,代码量成功翻了一倍!!!(滑稽
That′s allThat's\ allThat′s all
Thank youThank\ youThank you
SGTtrick的更多相关文章
随机推荐
- JavaScript基础应用
1.实现字符串的反向输出 var s="abc" s.split('').reverse().join('') -----> "cab" 知识点: S ...
- codeblock用法
1.链接动态库.so和静态库.a settings->compiler->linker settings->add 2.编译报错ld return 1 exit status 一般来 ...
- 一个java使用redis的简单案例
这个例子中,java使用Jedis来操作Redis 1.引入Jedis的依赖 <dependency> <groupId>redis.clients</groupId&g ...
- 学习excel的使用技巧三快捷键和思路
快捷键 CRTL+回车 是多行执行 思路 关于公式 在空白出 写= 即开始写公式 excel第一行 就是行标 比如 A1 就是excel 表格中第一个 比如来个乘法 =A1*12+b1*13 求和更简 ...
- __getitem__ __setitem__ __delitem__ 使用
#__getitem__ __setitem__ __delitem__运行设置key value值了class fun: def __init__(self): print('test') def ...
- windows下wmic命令
转载 https://www.cnblogs.com/archoncap/p/5400769.html 第一次执行WMIC命令时,Windows首先要安装WMIC,然后显示出WMIC的命令行提示符.在 ...
- oracle数据链接
using System; using System.Collections.Generic; using System.Data; using System.Data.OracleClient; u ...
- 尚硅谷springboot学习30-docker安装mysql示例
docker pull mysql 错误的启动示例 错误日志:需要设置密码 正确的启动 但还不能直接使用,因为没有做端口映射,外界无法连接 可用的启动 连接成功 几个高级的操作 指定配置文件 dock ...
- PHP和Redis实现在高并发下的抢购及秒杀功能示例详解
抢购.秒杀是平常很常见的场景,面试的时候面试官也经常会问到,比如问你淘宝中的抢购秒杀是怎么实现的等等. 抢购.秒杀实现很简单,但是有些问题需要解决,主要针对两个问题: 一.高并发对数据库产生的压力二. ...
- mysql win10x64 免安装版 安装配置
安装包下载或者 gaobo百度云/工具/开发工具/mysql-5.7.23-winx64.zip 第一步, 解压MySQL压缩包 将以下载的MySQL压缩包解压到自定义目录下,我的解压目录是: ...