zoukankan      html  css  js  c++  java
  • 主席树

    主席树是一种可持久化线段树、其发明者orz 黄嘉泰 拼音缩写与某届主席一样、于是这个数据结构被戏称为主席树。

    所谓的“持久化数据结构”、就是保存这个数据结构的所有历史版本、同时利用它们之间的共用数据减少时间和空间的消耗。

    由于线段树在区间长度固定的情况下结构都是一致的、主席树能够通过两颗线段树相减来得到某一区间的信息。

    至于主席树的作用、其能够查询不修改的区间K大值、区间不同数的个数、套个树状数组还能查询动态K大值......

    给出几篇文章以便学习 ==> 链接II、链接II、链接III

    一些题目 :

    HDU 2665 (可作为模板使用)

    题意 : 给出一个整数序列、有若干个问询、每次问询 (L, R, K) 表示 L~R 区间内第 K 大的值是多少

    分析 : 比较裸的主席树题目

    首先先对于每个前缀按权值建出主席树、然后问询的时候就可以通过减法得到区间 (L, R) 的信息

    由于存储的是值域信息、查询K大值的时候就判断左右子区间的元素个数与K的大小便能知道往哪个方向走

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 10;
    struct NODE{
        int sum, L, R;
        NODE(){};
        NODE(int _sum, int _L, int _R):
            sum(_sum),L(_L),R(_R){};
    }T[maxn*18]; int Tcnt = 0;
    
    int root[maxn];
    int arr[maxn], N;
    int uni[maxn], uniLen;
    
    int newNode(int sum, int L, int R)
    {
        T[++Tcnt] = NODE(sum, L, R);
        return Tcnt;
    }
    
    inline void Insert(int &root, int pre, int pos, int L, int R)
    {
        root = newNode(T[pre].sum+1, T[pre].L, T[pre].R);
        if(L == R) return ;
        int M = L + ((R-L)>>1);
        if(pos <= M) Insert(T[root].L, T[pre].L, pos, L, M);
        else Insert(T[root].R, T[pre].R, pos, M+1, R);
    }
    
    int Kth(int x, int y, int L, int R, int K)
    {
        if(L == R) return L;
        int M = L + ((R-L)>>1);
        int L_sum = T[T[y].L].sum - T[T[x].L].sum;
        if(K <= L_sum) Kth(T[x].L, T[y].L, L, M, K);
        else Kth(T[x].R, T[y].R, M+1, R, K - L_sum);
    }
    
    int main(void)
    {
        int nCase;
        scanf("%d", &nCase);
        while(nCase--){
    
            T[0] = NODE(0, 0, 0);///将 0 号节点的左右子树指向自己
            Tcnt = root[0] = 0;///便不用显式建树
    
            int Q;
            scanf("%d %d", &N, &Q);
            for(int i=1; i<=N; i++){
                scanf("%d", &arr[i]);
                uni[i-1] = arr[i];
            }
    
            sort(uni, uni+N);
            uniLen = unique(uni, uni+N) - uni;///离散化
    
            for(int i=1; i<=N; i++){
                int pos = lower_bound(uni, uni+uniLen, arr[i]) - uni;
                pos++;
                Insert(root[i], root[i-1], pos, 1, uniLen+1);
            }
    
            int l, r, k;
            while(Q--){
                scanf("%d %d %d", &l, &r, &k);
                int pos = Kth(root[l-1], root[r], 1, uniLen+1, k);
                printf("%d
    ", uni[--pos]);
            }
        }
        return 0;
    }
    View Code

    SPOJ D-QUERY

    题意 : 给出 n 个整数、每次问询一个区间 (L, R) 问这个区间内不同数的个数是多少?

    分析 :

    这题很久之前用线段树离线做过 ==> Click here

    如果使用主席树的话就能做到在线回答问询

    具体做法的核心思路其实和线段树离线的时候差不多

    但是这里主席树存储的并不是值域信息、而是区间具体每个位置是否包含一种数

    也就是这题主席树区间代表的信息和普通线段树所代表的信息一样

    主席树每次按照前缀建树、建树的时候保证每一种数只保留最右边的位置

    然后对于问询 (l, r) 只要在 root[r] 这颗树上查询端点 l 右边的所有 sum 值的和即可

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 10;
    struct NODE{
        int sum, L, R;
        NODE(){};
        NODE(int _sum, int _L, int _R):
            sum(_sum),L(_L),R(_R){};
    }T[maxn*18]; int Tcnt = 0;
    
    int root[maxn];
    int arr[maxn], N;
    
    int newNode(int sum, int L, int R)
    {
        T[++Tcnt] = NODE(sum, L, R);
        return Tcnt;
    }
    
    inline void Insert(int &root, int pre, int pos, int val, int L, int R)
    {
        root = newNode(T[pre].sum+val, T[pre].L, T[pre].R);
        if(L == R) return ;
        int M = L + ((R-L)>>1);
        if(pos <= M) Insert(T[root].L, T[pre].L, pos, val, L, M);
        else Insert(T[root].R, T[pre].R, pos, val, M+1, R);
    }
    
    int query(int rt, int pos, int L, int R)
    {
        if(L == R) return T[rt].sum;
        int ret = 0;
        int M = L + ((R-L)>>1);
        if(pos <= M) ret += query(T[rt].L, pos, L, M) + T[T[rt].R].sum;///递归进入左边区间的时候、要把右边区间的和加上
        else ret += query(T[rt].R, pos, M+1, R);
        return ret;
    }
    
    map<int, int> mp;
    int main(void)
    {
        T[0] = NODE(0, 0, 0);
        root[0] = 0; Tcnt = 0;
        scanf("%d", &N);
        for(int i=1; i<=N; i++) scanf("%d", &arr[i]);
        for(int i=1; i<=N; i++){
            if(mp.count(arr[i])){
                int tmpRoot;
                Insert(tmpRoot, root[i-1], i, 1, 1, N);
                Insert(root[i], tmpRoot, mp[arr[i]], -1, 1, N);
            }else Insert(root[i], root[i-1], i, 1, 1, N);
            mp[arr[i]] = i;
        }
    
        int Q;
        scanf("%d", &Q);
        while(Q--){
            int l, r;
            scanf("%d %d", &l, &r);
            printf("%d
    ", query(root[r], l, 1, N));
        }
        return 0;
    }
    View Code
  • 相关阅读:
    docker-compose
    Cassandra
    npm常用命令
    k8s linux win10
    wsl2 docker 迁移
    docker http 代理
    mysql查看当前所有的数据库和索引大小
    mybatis 遍历list拼接 or查询
    es head crud
    nginx 代理转发mysql
  • 原文地址:https://www.cnblogs.com/qwertiLH/p/9154722.html
Copyright © 2011-2022 走看看