On the tree line can be persistent (Chairman of the tree)

Foreword


Every game is always there are so few engage in topic data structure, from the very beginning to understand the monotony of stacks, queues monotonous, tree line and then later President of the tree, the tree split chain, the suffix automaton, although some have not learned , it appears that the data structure is not so good I want to do, just get to know a new knowledge necessary to spend doing something, do it slowly problem will find, in fact, the data structure or hard plus thinking to use.

Chairman of the tree is said to be a man named Huang Jiatai classmates invention, as to why called the "Chairman of the tree it," you look at the big brother of the abbreviation $ (HJT) $ Which is not our previous chairman, is not it, ye are not I dare say not

Throws problem


 To set the number of $ N $, $ M $ total interrogation time, asking each time interval $ [l, r] $ of a large number of $ k $. Where $ N, M, l, r $ not exceeding $ 2 \ times 10 ^ {5} $, ensure inquiry answer.

Solve the problem


 Violence Act

 Obviously, the most violent way is sort of range and then sort the output after $ k $ number. The worst-case time complexity is $  O (nmlgn) $, no timeout strange.

 Chairman of the tree (be persistent segment tree) method

 So address this issue, the birth of a new data structure, which is the Chairman of the tree.

 Chairman of the tree, whose real name may persist tree line, that is to say, the President of tree data structure is based on a segment tree evolved. Their prefix "be persistent" segment tree is intended to increase the number of history points to maintain historical data, allowing us to query historical data in a relatively short period of time.

Let's look at some words to understand the next Chairman of the tree

First, learn the Chairman of the tree is the gist of the pre-skills weights tree line. The reason why the weight segment tree will bring "weights" word, because it is the tree line record weights. Thus the need to use discrete operations to process a [1-n]. Record the weight means that each point is stored the total number of numbers in the interval of occurrence. For example an array of length 10. [1,1,2,3,3,4,4,4,4,5].

其中1出现了两次,那么[1,1]这个节点的值为2,2出现了1次,那么[2,2]这个节点的值为1,那么显然[1,2]这个节点的值为3,即1出现的次数和2出现的次数加和。那么如果我想要知道这个数组上的第k小,我就可以在这棵权值线段树上用logn的时间来实现。比如我想要求这个区间上的第7小,那么我先找到这棵树的根节点,根节点上的数字显示的是10,表示在[1,8]这个区间上一共有10个数字,那么我只要去看它的左孩子上的个数是多少。这时我看到左孩子上的数字是9,说明前9小的数字都在左子树上,那么我要找的第7小也在左子树上,那么我就递归去找左子树。当我再看左孩子的时候,看到数字是3,说明前3小的数字在左子树上,那么我要找的就是右子树上的第k-sum[i]小,即7-3=4,找到右子树上的第4小即可。直到找到某一个叶子节点,说明找到了我要找的第k小。这是通过权值线段树找到区间[1,n]上的第k小/大的应用。

 

那么知道了权值线段树是什么之后,主席树又是什么呢。主席树是一棵可持久化线段树,可持久化指的是它保存了这棵树的所有历史版本,最简单的办法是:如果你输入了n个数,那么每输入一个数字a[i],就构造一棵保存了从a[1]到a[i]的权值线段树。之所以这么做,是因为我们可以把第j棵树和第(i-1)棵树上的每个点的权值相减,来得到一颗新的权值线段树,而这个新的权值线段树相当于是输入了a[i]到a[j]以后得到的。如果这么说不太好理解的话,我们可以思考另外一个模型:求数组a[1]到a[n]的和。如果只是求[1,n]这一段的和,那么我们直接全部加起来就可以了,或者求一个前缀和sum[n]即可。那么如果我给定了l和r,想要知道[l,r]这段区间上的和呢?是不是利用前缀和sum[r]-sum[l-1]就可以轻松得到?那么主席树的思想也是如此,将tree[r]-tree[l-1]得到的一棵权值线段树即为属于[l,r]的一棵权值线段树,那么在这么一棵权值线段树上求第k大不是就转变为之前的问题了么。如果还是没有理解为什么可以用tree[r]-tree[l-1]来表示属于[l,r]的权值线段树,可以自己构造一个数组,然后画出属于[1,l-1],[1,r]和[l,r]的三颗权值线段树,来自己研究研究,多自己动手也不是一件坏事嘛。

 

想必看到这里你对主席树应该有了大概的印象

接下来我们先看道权值线段树的例题:[NOI2004]郁闷的出纳员 

说起权值线段树,其实写完后发现真的和线段树差不多,线段树维护的是下标区间内的信息,而权值线段树维护的是权值区间内的信息,例如维护权值属于[2,7]这个区间中各个数出现的次数

本题维护一个偏移量$infu$,当 $A$ 操作时,加工资$ infu+=x$;$S$ 操作 $infu-=x$,这时有区间修改,低于最低工资的要清0 。注意在有新员工插入的时候加入$x -infu$即可,这样仍然是全局偏移量

所有数据在处理的时候都要加上$base$,$base$ 是防止负数出现

Code

#include <cstdio>
#include <algorithm>
#define lson rt<<1, l , mid
#define rson rt<<1|1, mid+1 , r
using namespace std;
const int maxn = 4e5 + 20;
const int base = 2e5 + 10;
int n, mink, k;
char ch;
int tree[maxn<<2], lazy[maxn<<2];
void push_up(int rt){
    tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
void push_down(int rt){
    if(!lazy[rt]) return;
    tree[rt<<1] = tree[rt<<1|1] = 0;
    lazy[rt] = 0;
    lazy[rt<<1] = lazy[rt<<1|1] = 1;
}
//单点修改 
void insert(int rt, int l, int r, int pos){
    if(l==r){
        tree[rt]++; //tree[rt]才代表区间[pos,pos]节点的值 
        return ;
    }
    push_down(rt); //单调修改也需要push_down, 不然会WA(废话)
    int mid = (l+r)>>1;
    if(pos<=mid) insert(lson, pos);
    else insert(rson, pos);
    push_up(rt); //其实用tree[rt]++也可,这点在主席树里面会有所体现 
}
//区间修改 
void update(int rt, int l, int r, int ul, int ur){
    if(ul<=l&&r<=ur){
        tree[rt] = 0;
        lazy[rt] = 1;
        return ;
    }
    push_down(rt);
    int mid = (l+r)>>1;
    if(ul<=mid) update(lson, ul, ur);
    if(ur>mid) update(rson, ul, ur);
    push_up(rt); 
}
//全区间查询第k大 
int query(int rt, int l, int r, int k){
    if(l==r) return l;
    push_down(rt);
    int mid = (l+r)>>1;
    if(tree[rt<<1|1]>=k) return query(rson, k);
    else return query(lson, k-tree[rt<<1|1]);
}
 
int main(){
    scanf("%d%d", &n, &mink);
    int ans = 0, infu = 0;
    while(n--){
        scanf(" %c%d", &ch ,&k);
        if(ch=='I'){
            if(k<mink) continue;
            ans++;
            insert(1, 1, maxn, k-infu+base);         //k-infu+base(注意读题,是当集体扣工资时才会有人离开公司) 
        }
        else if(ch=='A') infu += k;
        else if(ch=='S'){
            infu -= k;
            update(1, 1, maxn, 1, mink-infu+base-1); //x+infu<mink, x<mink-infu,区间是闭区间,所以 mink-infu+base-1
        }
        else {
            if(tree[1]<k) printf("-1\n");
            else printf("%d\n", query(1, 1, maxn, k)-base+infu);
        }
    }
    printf("%d\n",ans-tree[1]);
    return 0;
}
 
View Code

 

我们的前置技能点完了,下面正式来看我们又爱又恨的主席树

 

[POJ 2104]K-th Number

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 4e6+10;
int n, q, pntnum = 0;//pnt是Persistable_Segment_Tree的缩写 
int a[maxn], b[maxn];
int rt[maxn], ls[maxn], rs[maxn], tree[maxn];
void build(int &pos,int l,int r){ //这里传的是&pos!!! 
    pos = ++pntnum;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(ls[pos],l,mid);
    build(rs[pos],mid+1,r);
}
//单点修改
void insert(int &pos,int vsn,int l,int r,int loc){//pos新版本的当前节点编号,vsn旧版本的当前节点编号,l左端点,r右端点,loc要修改的节点编号
    pos = ++pntnum; //新增节点 
    if(l==r){
        tree[pos]=tree[vsn]+1;//当前节点加1
        return;
    }
    ls[pos]=ls[vsn]; //继承左子树
    rs[pos]=rs[vsn]; //继承右子树
    tree[pos]=tree[vsn]+1; //当前路径上的所有点权值加1,这里相当于push_up,只不过对于这道题直接在父节点写比较方便 
    int mid=(l+r)>>1;
    if(loc<=mid) insert(ls[pos],ls[vsn],l,mid,loc);
    else insert(rs[pos],rs[vsn],mid+1,r,loc);
}
//查询第k小 
int query(int lv,int rv,int l,int r,int k){
    if(l==r) return l;
    int mid=(l+r)>>1, sum = tree[ls[rv]]-tree[ls[lv]]; 
    if(sum>=k) return query(ls[lv],ls[rv],l,mid,k);
    else return query(rs[lv],rs[rv],mid+1,r,k-sum);
}
int main(){
    scanf("%d%d", &n, &q);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]), b[i] = a[i];
    sort(b+1, b+n+1);
    int m = unique(b+1, b+n+1)-b-1;
    build(rt[0], 1, m);
    for(int i = 1; i <= n; i++){
        int p = lower_bound(b+1, b+1+m, a[i])-b;//离散化映射 
        insert(rt[i], rt[i-1], 1, m, p);
    }
    while(q--){
        int l, r, k;
        scanf("%d%d%d", &l, &r, &k);
        int ans = query(rt[l-1], rt[r], 1, m, k);
        printf("%d\n", b[ans]);
    }
    return 0;
}
View Code

 

Guess you like

Origin www.cnblogs.com/wizarderror/p/11779845.html