HDU-2665(模板题 划分树 || 主席树)

题目链接

题意: 求区间第K大

划分树

划分树好像只能用于求这个

划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序
同时,用一个标记数组记录,每次划分时,当前位置累积被分配到左子树的个数,查询时就判断该区间第k大是被划分到了左子树,还是右子树,直到找到。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
const int maxn = 100050;
using namespace std;
int tree[20][maxn];
int tsort[maxn];
int toleft[20][maxn];
int n, m;

void build(int l, int r, int deep){
    if(l == r)  //到达叶子
        return ;
    int m = (l+r)>>1;
    int same = m - l + 1; //得到应该被划分到左子树的个数  但same是用来表示,与中位数相等但是应该被划分去左子树的个数
    for(int i=l; i<=r; i++)         //求出真正的same
        if(tree[deep][i] < tsort[m])
            same--;
    int lpos=l, rpos=m+1;       //左右子树的起点
    for(int i=l; i<=r; i++){
        if(tree[deep][i]<tsort[m])      //划分到左子树
            tree[deep+1][lpos++] = tree[deep][i];
        else{
            if(tree[deep][i]==tsort[m] && same)     //与中位数相等,但是要划分到左子树
                tree[deep+1][lpos++] = tree[deep][i], same--;
            else
                tree[deep+1][rpos++] = tree[deep][i];       //划分右子树
        }
        toleft[deep][i] = toleft[deep][l-1]+(lpos-l);   //计算当前位置划分到左子树的个数
    }
    build(l, m, deep+1);
    build(m+1, r, deep+1);
}

int query(int l, int r, int deep, int L, int R, int k){
    //printf("%d %d %d %d %d****
", l, r, L, R, k);
    if(l == r)
        return tree[deep][l];

    int m = (l+r)>>1;
    int cnt = toleft[deep][R] - toleft[deep][L-1];   //被划分到左子树的个数
    //printf("%d %d###
", m, cnt);
    //newL newR newk 是被划分到左子树(或右子树),在子树中的新区间
    if(cnt>=k){
        int newL = l + toleft[deep][L-1] - toleft[deep][l-1];
        int newR = newL + cnt - 1;
        return query(l, m, deep+1, newL, newR, k);
    }
    else{
        int newR=R+toleft[deep][r]-toleft[deep][R];
        int newL=newR-(R-L-cnt);
        int newk = k-cnt;
        return query(m+1, r, deep+1, newL, newR, newk);
    }
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        for(int i=1; i<=n; i++){
            scanf("%d", &tree[0][i]);
            tsort[i] = tree[0][i];
        }
        sort(tsort+1, tsort+n+1);
        build(1, n, 0);

        /*
        for(int i=0; i<=3; i++){
            for(int j=1; j<=n; j++){
                printf("%d ", tree[i][j]);
            }
            printf("******
");
        }
        printf("



");
        for(int i=0; i<=3; i++){
            for(int j=1; j<=n; j++){
                printf("%d ", toleft[i][j]);
            }
            printf("******
");
        }
        */

        //printf("####
");
        while(m--){
            int a, b, k;
            scanf("%d%d%d", &a, &b, &k);
            printf("%d
", query(1, n, 0, a, b, k));
        }
    }
    return 0;
}

/*
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
 */

主席树

#include <bits/stdc++.h>
const int maxn = 100050;
using namespace std;
typedef long long ll;

int a[maxn], b[maxn], rt[maxn*20], ls[maxn*20], rs[maxn*20], sum[maxn*20];
// rt[i] 第i个版本的根节点所在的位置
// ls[i] rs[i] 节点的左右子树的位置
int n, m, tot, len;

void build(int &o, int l, int r){       //确定最初的版本
    o = ++tot;          //这里 tot 的统计是用的dfs(先序)
    sum[o] = 0;
    if(l == r)
        return ;
    int m = (l+r)>>1;
    build(ls[o], l, m);
    build(rs[o], m+1, r);
}

void update(int &o, int l, int r, int last, int p){
    o = tot++;
    ls[o] = ls[last];
    rs[o] = rs[last];//先继承前个版本的左右子树
    sum[o] = sum[last]+1;
    if(l == r)
        return ;
    int m = (l+r)>>1;
    if(p<=m)        //更新左右子树
        update(ls[o], l, m, ls[last], p);
    else
        update(rs[o], m+1, r, rs[last], p);
}

int query(int ss, int tt, int l, int r, int k){
    if(l == r)
        return l;
    int m = (l+r)>>1;
    int cnt = sum[ls[tt]] - sum[ls[ss]];        //两个版本中左子树的相差的个数
    if(cnt >= k)        //答案在左子树中
        return query(ls[ss], ls[tt], l, m, k);
    else    //答案在右子树中
        return query(rs[ss], rs[tt], m+1, r, k-cnt);
}

int main()
{
    int t;
    scanf("%d", &t);
    while(t--){
        scanf("%d%d", &n, &m);
        for(int i=1; i<=n; i++){
            scanf("%d", &a[i]);
            b[i] = a[i];
        }
        sort(b+1, b+n+1);
        tot = 0;
        len = unique(b+1, b+n+1) - (b+1);   //要离散化的去重数组
        build(rt[0], 1, len);
        for(int i=1; i<=n; i++){
            a[i] = lower_bound(b+1, b+len+1, a[i]) - b;
            update(rt[i], 1, len, rt[i-1], a[i]);
        }
        while(m--){
            int l, r, k;
            scanf("%d%d%d", &l, &r, &k);
            int ans = query(rt[l-1], rt[r], 1, len, k);
            printf("%d
", b[ans]);
        }
    }
    return 0;
}

/*
 * 在这里主席树其实相当于多颗权值线段树
 * 由于每次版本的更新只会更新一个点,所以对于每个点,只会有一个子节点会发生更新
 * 主席树就是重复利用了不会更新的节点,只去创建会更新的节点
 */
原文地址:https://www.cnblogs.com/jizhihong/p/13337356.html