• 树套树Day2


    滚回来更新,,,

    在Day1我们学了最基本的线段树套平衡树

    Day2开始我们要学习一些黑科技

    (所以很大概率会出现Day3 w

    1.线段树上的黑科技  

      这一段我们分几项来讲

    1.权值线段树

      权值线段树以权值为下标建树(就像求逆序对时用的树状数组),一开始所有节点都为0,通过线段树的区间极值,区间和来表示“这个区间上有多少个数”等信息。

      下面这个代码并没有离散化因为我懒得写↓

    #include <iostream>
    #include <cstdio>
    
    using namespace  std;
    
    const int maxn = 100010;
    
    struct Node{
        int l,r;
        long long tot;
    } tree[maxn*3];
    
    void build(int l,int r,int o)
    {
        tree[o].l=l;
        tree[o].r=r;
        if(tree[o].l==tree[o].r) return ;
        int mid=(tree[o].l+tree[o].r)>>1;
        build(l,mid,o<<1);
        build(mid+1,r,o<<1|1);
    }
    
    void push_up(int o)
    {
        tree[o].tot=tree[o<<1].tot+tree[o<<1|1].tot;
    }
    
    void update(int o,int x)
    {
        if(tree[o].l==x && tree[o].l==tree[o].r)
        {
            tree[o].tot++;
            return ;
        }
        int mid=(tree[o].l+tree[o].r)>>1;
        if(x<=mid) update(o<<1,x);
        if(x>mid) update(o<<1|1,x);
        push_up(o);
    }
    
    long long getans(int o,int l,int r)
    {
        if(tree[o].l>r || tree[o].r<l) return 0;
        if(tree[o].l==l && tree[o].r==r) return tree[o].tot;
        int mid=(tree[o].l+tree[o].r)>>1;
        if(r<=mid) return getans(o<<1,l,r);
        if(l>mid) return getans(o<<1|1,l,r);
        return getans(o<<1,l,mid)+getans(o<<1|1,mid+1,r);
    }
    
    int main()
    {
        int n;
        scanf("%d",&n);
        build(1,maxn,1);
        long long ans=0;
        for(int i=1;i<=n;i++)
        {
            int x;
            scanf("%d",&x);
            ans+=getans(1,x+1,maxn);
            update(1,x);
        }
        printf("%lld",ans);
    }
    求逆序对

    2.标记永久化

      线段树的pushup和pushdown操作有时候实现代价很大,我们能不能不用这两个东西呢?

      可以。具体做法就是每个节点记一个sum记一个add,

      修改的时候:

      1.当目前询问区间与当前区间完全重合的时候,更新add的值,返回。

      2.在一路下来的时候把所有经过的区间(相当于包含询问区间的区间)的sum加上此次修改所产生的影响 v*(xr-xl+1)。

      (注意完全重合之后就返回了,也就是说下面的部分的影响还没有更新。)

      查询的时候: 

      由于上面的更新没有对下面产生影响,所以我们需要一路累加add,直到目前询问区间与当前区间完全重合的时候,答案为sum+add*区间长度

      注意累加add不用累加上完全重合的区间的add,因为它已经在修改的时候对sum进行更新了

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #define pos(i,a,b) for(int i=(a);i<=(b);i++)
    #define N 201000
    using namespace std;
    int n,m;
    int sum[N*4],add[N*4];
    int a[N];
    void build(int l,int r,int rt){
        if(l==r){
            sum[rt]=a[l];return;
        }
        int mid=(l+r)>>1;
        build(l,mid,rt<<1);
        build(mid+1,r,rt<<1|1);
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    void update(int rt,int l,int r,int v,int xl,int xr){
        sum[rt]+=v*(xr-xl+1);
        if(l==xl&&r==xr){
            add[rt]+=v; return;
        }
        int mid=(l+r)>>1;
        if(xr<=mid)  update(rt<<1,l,mid,v,xl,xr);
        else{
            if(xl>mid)   update(rt<<1|1,mid+1,r,v,xl,xr);
            else update(rt<<1,l,mid,v,xl,mid),update(rt<<1|1,mid+1,r,v,mid+1,xr);
        }
    }
    int query(int rt,int ad,int l,int r,int xl,int xr){
        if(xl==l&&xr==r){
            return sum[rt]+ad*(xr-xl+1);
        }  
        int mid=(l+r)>>1;
        if(xr<=mid) return query(rt<<1,ad+add[rt],l,mid,xl,xr);
        else{
            if(xl>mid) return query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr);
            else return query(rt<<1,ad+add[rt],l,mid,xl,mid)+query(rt<<1|1,ad+add[rt],mid+1,r,mid+1,xr);
        }
    }
    int main(){
        scanf("%d%d",&n,&m);
        pos(i,1,n) scanf("%d",&a[i]);
        build(1,n,1);
        pos(i,1,m){
            int opt;scanf("%d",&opt);
            int x,y;scanf("%d%d",&x,&y);
            if(opt==1){
                int k;scanf("%d",&k);
                update(1,1,n,k,x,y);
            }
            else printf("%d
    ",query(1,0,1,n,x,y));
        }
        return 0;
    }
    标记永久化

      这个做法在主席树,树套树中很有用

    3.主席树

      又称函数式线段树,具体就是有多个版本的线段树,可以支持“回溯”到之前的某个版本,网上介绍很多,这里不多说了。

    2.二维线段树

      也就是线段树套线段树,对于线段树的每个区间维护一个线段树,这样就可以求矩形和/矩形极值了。具体个人有个人的写法。

    3.动态开节点

      有的时候有些节点你只是放一个标记在那里,不需要实际操作,你就可以开一个“大节点”表示那一块不用实际操作的节点,当需要操作的时候再从那个“大节点”里搞出几个"小节点"来操作。

      这样可以避免MLE

      具体可以见NOIp2017D2T3列队的平衡树写法,我的blog里应该有

    4.怎样的两棵树可以套

      数据结构的嵌套,当你对数据结构掌握得很熟练的时候其实自然就明白了。其实树套树不过是用“内层树”维护“外层树”节点上的信息。(一般“外层树”节点上的信息是用数/数组维护的)

      而外层树的结构稳定,不会出现Splay/Treap/AVL这种东西

      而且很多可以顶一层数据结构的东西其实也可以嵌套

      比如CDQ套某某某,替罪羊套某某某

  • 相关阅读:
    JS高级知识部分【4】
    关于python3 使用pycharm+unittest+html+HTMLTestRunner 测试用例运行正常,但却不能生成测试报告的解决方法
    python生成HTMl报告(unittest)
    Linux系统下安装jenkins使用
    jenkins使用
    UI定位元素大全(跟App定位元素差不多哦)
    UI自动化前置代码
    python+selenium+pytest+html报告
    jenkins解决python不是内部命令
    如何做好APP功能测试?
  • 原文地址:https://www.cnblogs.com/Kong-Ruo/p/8047182.html
Copyright © 2020-2023  润新知