poj 2104 主席树

   附上题目链接: http://poj.org/problem?id=2104   很经典的一道主席树题, 题意是查询区间的第k大, http://blog.csdn.net/famousdt/article/details/7064866 这个博主讲的相当赞, 这里附上他的原话:

建树的过程比较简单,对于区间[l,r],首先通过对原数组的排序找到这个区间的中位数a[mid],小于a[mid]的数划入它的左子树[l,mid-1],大于它的划入右子树[mid,r]。
同时,对于第i个数a[i],记录在[l,i]区间内有多少数被划入左子树。最后,对它的左子树区间[l,mid-1]和右子树区间[mid,r]递归的继续建树就可以了。
建树的时候要注意,对于被分到同一子树的元素,元素间的相对位置不能改变。

查找的过程中主要问题就是确定将要查找的区间。
查找深度为dep,在大区间[L ,R]中找小区间[l ,r]中的第k元素。
我们的想法是,先判断[l ,r]中第k元素在[L ,R]的哪个子树中,然后找出对应的小区间和k,递归的进行查找,直到小区间的l==r为止。 
通过之前的记录可以知道,在区间[L,l-1]中有(toleft[dep][l-1]-toleft[dep][L-1])进入左子树,
记它为x。

同理区间[L,r]中有(toleft[dep][r]-toleft[dep][L-1])个数进去左子树,记它为y。
所以,我们知道区间小区间[l,r]中有(y-x)个数进入左子树。那么如果(y-x)>=k,那么就在左子树中继续查找,否则就在右子树中继续查找。

接着,解决查找的小区间的问题。 
  如果接下来要查找的是左子树,那么小区间应该是[L+([L,l-1]区间进入左子树的个数),L+([L,r]区间内进入左子树的个数)-1]。即区间[L+x,L+y-1]。
    显然,这里k不用变。 
  如果接下来要查找的是右子树,那么小区间应该是[mid+([L,l-1]区间中进入右子树的个数),mid+([L,r]区间进入右子树的个数)-1]。
    即区间[mid+(l-L-x),mid+(r-L-y)]。
    显然,这里k要减去区间里已经进入左子树的个数,即k变为k-(y-x)。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define MAX_SIZE 100005
int sorted[MAX_SIZE];//已经排好序的数据
int toleft[25][MAX_SIZE];
int tree[25][MAX_SIZE];
void build_tree(int left, int right, int deep)
{
    int i;
    if (left == right) return ;
    int mid = (left + right) >> 1;
    int same = mid - left + 1; //位于左子树的数据
    for (i = left; i <= right; ++i) {//计算放于左子树中与中位数相等的数字个数
        if (tree[deep][i] < sorted[mid]) {
            --same;
        }
    }
    int ls = left;
    int rs = mid + 1;
    for (i = left; i <= right; ++i) {
        int flag = 0;
        if ((tree[deep][i] < sorted[mid]) || (tree[deep][i] == sorted[mid] && same > 0)) {
            flag = 1;
            tree[deep + 1][ls++] = tree[deep][i];
            if (tree[deep][i] == sorted[mid])
                same--;
        } else {
            tree[deep + 1][rs++] = tree[deep][i];
        }
        toleft[deep][i] = toleft[deep][i - 1]+flag;
    }
    build_tree(left, mid, deep + 1);
    build_tree(mid + 1, right, deep + 1);
}
int query(int left, int right, int k, int L, int R, int deep)
{
    if (left == right)
        return tree[deep][left];
    int mid = (L + R) >> 1;
    int x = toleft[deep][left - 1] - toleft[deep][L - 1];//位于left左边的放于左子树中的数字个数
    int y = toleft[deep][right] - toleft[deep][L - 1];//到right为止位于左子树的个数
    int ry = right - L - y;//到right右边为止位于右子树的数字个数
    int cnt = y - x;//[left,right]区间内放到左子树中的个数
    int rx = left - L - x;//left左边放在右子树中的数字个数
    if (cnt >= k) {
        //printf("sss %d %d %d\n", xx++, x, y);
        return query(L + x, L + y - 1, k, L, mid, deep + 1);
    }
    else {
        //printf("qqq %d %d %d\n", xx++, x, y);
        return query(mid + rx + 1, mid + 1 + ry, k - cnt, mid + 1, R, deep + 1);
    }
}

int main() {
    int n, m;
    while(scanf("%d%d", &n, &m) == 2){
        for(int i=1; i<=n; i++) {
            scanf("%d", &sorted[i]);
            tree[0][i] = sorted[i];
        }
        sort(sorted+1, sorted+1+n);
        build_tree(1, n, 0);
        for(int i=0; i<m; i++){
            int a, b, k;
            scanf("%d%d%d", &a, &b, &k);
            printf("%d\n", query(a, b, k, 1, n, 0));
        }
    }

    return 0;
}
时间: 2024-10-14 00:44:28

poj 2104 主席树的相关文章

[poj 2104]主席树+静态区间第k大

题目链接:http://poj.org/problem?id=2104 主席树入门题目,主席树其实就是可持久化权值线段树,rt[i]维护了前i个数中第i大(小)的数出现次数的信息,通过查询两棵树的差即可得到第k大(小)元素. #include<cstdio> #include<vector> #include<algorithm> using namespace std; #define lson(i) node[i].lson #define rson(i) node

poj 2104主席树求区间第k小

POJ - 2104 题意:求区间第k小 思路:无修改主席树 AC代码: #include "iostream" #include "iomanip" #include "string.h" #include "stack" #include "queue" #include "string" #include "vector" #include "set&

Poj 2104(主席树入门

题目:静态查询区间第k大. 主席树入门题目,之前看的很多资料一上来就是动态区间第k大,看得很费劲,后来找了个写得清晰的,感觉静态的还不算难,代码也不长. /* * @author: Cwind */ //#pragma comment(linker, "/STACK:102400000,102400000") #include <iostream> #include <map> #include <algorithm> #include <cs

K-th Number POJ - 2104 划分树

K-th Number You are working for Macrohard company in data structures department. After failing your previous task about key insertion you were asked to write a new data structure that would be able to return quickly k-th order statistics in the array

POJ 2104 归并树

链接: http://poj.org/problem?id=2104 代码: 31 int a[MAXN], num[MAXN]; 32 VI tree[MAXN << 2]; 33 34 void pushup(int l, int r, int rt) { 35 tree[rt].resize(r - l + 1); 36 merge(all(tree[rt << 1]), all(tree[rt << 1 | 1]), tree[rt].begin()); 37

POJ 2104 划分树模板题

给出n,m n个数字 m次询问,每次询问(l,r)区间的第k小的数 划分树模板 mark一下 #include "stdio.h" #include "string.h" #include "algorithm" using namespace std; int a[100010],as[100010]; int tree[20][100010];// 记录第i层元素序列 int sum[20][100010];// 记录第i层的1~j划分到左子

poj 2104 划分树模板

划分树的模板题. 1 #include <iostream> 2 #include <algorithm> 3 #include <cstdio> 4 using namespace std; 5 6 const int N = 100001; 7 int tree[20][N]; 8 int sum[20][N]; 9 int as[N]; 10 11 void build( int p, int l, int r ) 12 { 13 int mid = ( ( l

HDU 2665 &amp;&amp; POJ 2104(主席树)

http://poj.org/problem?id=2104 1 #include <cstdio> 2 #include <cstring> 3 #include <algorithm> 4 #include <string> 5 #include <cmath> 6 #include <iostream> 7 #include <stack> 8 using namespace std; 9 #define N 100

POJ 2104 求序列里第K大 主席树裸体题

给定一个n的序列,有m个询问 每次询问求l-r 里面第k大的数字是什么 只有询问,没有修改 可以用归并树和划分树(我都没学过..囧) 我是专门冲着弄主席树来的 对主席树的建树方式有点了解了,不过这题为什么是在主席树里面这么操作的 还是有点不懂,今天照着模板敲了一遍就打多校了 再研究吧 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using name