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的更多相关文章

随机推荐

  1. 为运行Microsoft Dynamics CRM 异步处理服务指定账户没有性能计数器权限

    CRM 2016 安装 为运行Microsoft Dynamics CRM 应用程序指定账户没有性能计数器权限 为运行Microsoft Dynamics CRM 异步处理服务指定账户没有性能计数器权 ...

  2. 6-安装hbase

    1.前提条件 ①安装zookeeper ②安装hdfs ③由于Hbase对时间的要求比较苛刻,所以3台节点时间必须同步,否则会报错 s10 zookeeper RegionServer HMaster ...

  3. Java 性能调优工具

    CPU使用率工具: vmstat 检查应用性能时,应该首先审查CPU时间.代码优化的目的是提升而不是降低(更短时间段内的)CPU的使用率.在试图深入优化应用前,应该先弄清楚为何CPU使用率低.磁盘使用 ...

  4. [Unity优化]UI优化(三):GraphicRebuild

    参考链接: https://blog.csdn.net/jingangxin666/article/details/80143176 调试过程: 1.修改Image的颜色 2.Graphic.SetV ...

  5. ---perl 模块安装方法

    http://blog.csdn.net/lincy100/article/details/7333794 $ perl -MCPAN -e shell install Log::Log4perlin ...

  6. SSO 单点登录

    一.单系统登录机制 1.http无状态协议 web应用采用browser/server架构,http作为通信协议.http是无状态协议,浏览器的每一次请求,服务器会独立处理,不与之前或之后的请求产生关 ...

  7. mongodb mongod 启动参数

    我们可以通过mongod --help查看mongod的所有参数说明,以下是各参数的中文解释. 基本配置 –quiet# 安静输出 –port arg# 指定服务端口号,默认端口27017 –bind ...

  8. 常用的stm32库函数

    //初始化的方式:先定义初始化机构体.再打开时钟使能.在对每一组GPIO口进行初始化. GPIO_InitTypeDef LED_GPIO; RCC_APB2PeriphClockCmd(RCC_AP ...

  9. ABAP 省市县级联搜索帮助

    在展示ABAP代码之前,先建立自建表ZCHENH006,表中包含两个关键字段 BELNR(地区编码),SDESC(地区描述). 编码规则参考:身份证前六位地区编码规则,可参考我另外一篇Blog导入系统 ...

  10. OpenStack 安装:基本环境准备

    刚刚学完openstack,这几篇文章就算对过去课程的一个总结吧. 首先说说基本的结构:在一台Dell的workstation上面安装了VMware,在VMware上面安装两台CentOS,现在给每台 ...