树套树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\n",query(1,0,1,n,x,y));
    }
    return 0;
}

标记永久化

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

3.主席树

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

2.二维线段树

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

3.动态开节点

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

  这样可以避免MLE

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

4.怎样的两棵树可以套

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

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

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

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

时间: 2024-11-08 21:28:42

树套树Day2的相关文章

BZOJ_3196_二逼平衡树(树套树:线段树+Treap)

描述 可以处理区间问题的平衡树. 分析 树套树.可以用线段树套Treap.人生第一道树套树的题... op1:如果在整区间,直接在该区间的treap上求解.否则分两个区间求解,然后相加.最后+1. op2:这个不太好直接做,可以二分,每次假定一个值,用这个值去做op1,以此求得一个rank=k+1的数,求rank=k的数等价与求这个数的前驱pre. op3:先删后加. op4&op5:如果在整区间,直接在该区间的treap上求解,否则分量个区间求解,pre取最大值,suc取最小值.注意有些数在有

BZOJ 3110: [Zjoi2013]K大数查询 [树套树]

3110: [Zjoi2013]K大数查询 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 6050  Solved: 2007[Submit][Status][Discuss] Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M接下来M行,每行形如1 a

[BZOJ3110] [Zjoi2013] K大数查询 (树套树)

Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M 接下来M行,每行形如1 a b c或2 a b c Output 输出每个询问的结果 Sample Input 2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3 Sample Output 1 2 1 HINT [

bzoj 3295: [Cqoi2011]动态逆序对(树套树 or CDQ分治)

Description 对于序列A,它的逆序对数定义为满足i<j,且Ai>Aj的数对(i,j)的个数.给1到n的一个排列,按照某种顺序依次删除m个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数. Input 输入第一行包含两个整数n和m,即初始元素的个数和删除的元素个数.以下n行每行包含一个1到n之间的正整数,即初始排列.以下m行每行一个正整数,依次为每次删除的元素. Output 输出包含m行,依次为删除每个元素之前,逆序对的个数. Sample Input 5 4 1 5 3

BZOJ3295 动态逆序对 树套树, 树状数组套线段树(主席树)

Orz黄学长,蒟蒻在黄学长的带领下,通过阅读黄学长的代码!终于会了这道题! 首先我想先说一下这道题的思路(准确来说是黄学长的). 很明显,树状数组应该不用讲吧!关键是内存怎么开,维护一些什么样的数据? 其实我们通过观察,很快可以发现,你维护被删的数比维护所有的数轻松多了(不管是空间上,还是时间上).所以我们就可以从这方面想!(其实我一开始的思路,因为这道题我已经看过很久了,一直想写,毕竟是白书里面的一道例题嘛!一开始,蒟蒻的我是打算这样的用树状数组套权值线段树,并且是维护所有的数,我发现空间不够

HDU HDOJ5412(树套树

题目:要求支持带修改维护区间第k大的值.所谓的动态区间第k大. 思路:题解说的是树状数组套treap,然而没想通树状数组怎么维护...线段树的话就是把所有的值离散化一下,离线建个关于值的线段树,每个节点是一个treap,treap里的值用位置做关键字,然后做区间查询,复杂度是O(nlogn*logn).基本也是经典的树套树做法....然后赛后写了两遍都没过.....今天心血来潮再挑战一下,结果从8点调到晚上1点.其间各种爆内存各种re各种t.....随机数据对拍了好久没拍出问题来....然后一直

bzoj 1901: Zju2112 Dynamic Rankings(树套树)

1901: Zju2112 Dynamic Rankings 经典的带修改求区间第k小值问题 树套树模板,我是用的线段树套splay实现的,而且用的数组模拟的,所以可能空间略大,bzoj过了,zoj过不了. 思路很简单,用线段树维护区间,用splay维护区间内的权值,然后询问的时候,二分答案key,然后在区间内找小于key的数有多少个. 贴上模板: #include<stdio.h> #include<string.h> #include<algorithm> #def

POJ 1195 2维线段树(树套树实现) 树状数组

1: #include <stdio.h> 2: #include <string.h> 3: #include <stdlib.h> 4: #include <algorithm> 5: #include <iostream> 6: using namespace std; 7:   8: #define LL(a) a<<1 9: #define RR(a) a<<1|1 10: const int MaxL = 10

[树套树]K大数查询

有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少.为了强制在线,每一次的a,b是加密的,需要异或lastans的后8位进行解密,其中lastans为上次输出的结果,初始为零.如果解密后a>b则先交换a,b数据保证解密后a,b不会超过N如果解密后a,b出现0,则赋值为1. 来历:bzoj上的一道题,经过子祯学长的魔改后,数据范围变得很奇怪... 算法:树