POJ_2104_Kth_(主席树)

描述


http://poj.org/problem?id=2104

给出一个n个数的数列,m次询问,每次询问求区间[l,r]中第k小的数,无修改操作.

K-th Number
Time Limit: 20000MS   Memory Limit: 65536K
Total Submissions: 46951   Accepted: 15697
Case Time Limit: 2000MS

Description

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 segment.
That is, given an array a[1...n] of different integer numbers, your program must answer a series of questions Q(i, j, k) in the form: "What would be the k-th number in a[i...j] segment, if this segment was sorted?"
For example, consider the array a = (1, 5, 2, 6, 3, 7, 4). Let the question be Q(2, 5, 3). The segment a[2...5] is (5, 2, 6, 3). If we sort this segment, we get (2, 3, 5, 6), the third number is 5, and therefore the answer to the question is 5.

Input

The first line of the input file contains n --- the size of the array, and m --- the number of questions to answer (1 <= n <= 100 000, 1 <= m <= 5 000).
The second line contains n different integer numbers not exceeding 109 by their absolute values --- the array for which the answers should be given.
The following m lines contain question descriptions, each description consists of three numbers: i, j, and k (1 <= i <= j <= n, 1 <= k <= j - i + 1) and represents the question Q(i, j, k).

Output

For each question output the answer to it --- the k-th number in sorted a[i...j] segment.

Sample Input

7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3

Sample Output

5
6
3

Hint

This problem has huge input,so please use c-style input(scanf,printf),or you may got time limit exceed.

Source

Northeastern Europe 2004, Northern Subregion

 

分析


静态的主席树裸题.

首先考虑把数据离散化,这样一共有n个数,分别为1,2,...,n-1,n(如果没有重复的话)(如果题目里面说有重复且重复数字排名相同,就去一下重就好了).用N=n的线段树来表示某一区间当前的情况,其中节点a[k]表示在这个区间内,属于[a[k].L,a[k].R]的数字共有多少.这样在这个区间上求第K小数的操作就类似于平衡树上的操作,走到一个节点,如果左孩子的数字个数a[a[k].l].s>=k,那么第K大的数就在左孩子区间,否则就在右孩子区间.这样一棵线段树可以表示数列中的一个区间.那要求任意区间的,每一个区间都要有自己的线段树吗?不必.类似求任意区间的和值,可以利用前缀和的思想,每一棵线段树代表数列从第一个数到第i个数的区间,这样只需要n棵线段树即可.而每一棵线段树的形状,大小,含义都是一样的,所以在数列区间[l,r]中属于[a[k].L,a[k].R]的数的个数就是在数列区间[1,r]中的个数减去在数列区间[1,l-1]中的个数,所以用两棵前缀和线段树相减就可以得到在数列区间[l,r]中的个数.

以上就是解决问题的基本思路.

但是n棵线段树就需要n^2级别的空间,会MLE,解决这个问题的就是主席树(可持久化线段树).(其实我不太懂这个名字的含义)

对于表示数列区间[1,i]的线段树,它相对于表示数列区间[1,i-1]的线段树来说,多了一个数字A[i],那就是在A[i]的节点上s值要+1,并且要向上更新,所有包含A[i]的节点都要更新,这样更新的就是从上到下一整条链,而其他的点并没有变,所以没次只用更新logn个点(第一次也是).所以n次建树共需要nlogn个点,空间就够用了(开空间之前动手算一下log).

p.s.

1.我的写法是在进入某一节点之前就修改它相关的值,也可以在函数的参数里使用引用,进入某一节点之后再修改,更简洁,但初学还是如下写法比较好理解.

2.学习了新的离散化写法,也可以用一个结构体把id数组和a数组放在一起,效果是一样的,但这样写更简洁.预处理的复杂度是O(n)的,当然如果有重复的话写成结构体好去重.当然也可以直接排序去重,之后再用O(nlogn)的时间二分查找一遍,这样就不需要id数组了.

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn=100000+5;
int n,m,cnt;
int a[maxn],b[maxn],id[maxn],root[maxn];
struct node{ int l,r,x; }t[maxn*20];
void update(int l,int r,int now,int pre,int x){
    if(l==r) return;
    int mid=l+(r-l)/2;
    if(x<=mid){
        t[now].l=++cnt;
        t[now].r=t[pre].r;
        t[t[now].l].x=t[t[pre].l].x+1;
        update(l,mid,t[now].l,t[pre].l,x);
    }
    else{
        t[now].l=t[pre].l;
        t[now].r=++cnt;
        t[t[now].r].x=t[t[pre].r].x+1;
        update(mid+1,r,t[now].r,t[pre].r,x);
    }
}
int query(int l,int r,int x,int y,int k){
    if(l==r) return l;
    int mid=l+(r-l)/2;
    int s=t[t[y].l].x-t[t[x].l].x;
    if(k<=s) return query(l,mid,t[x].l,t[y].l,k);
    else return query(mid+1,r,t[x].r,t[y].r,k-s);
}
bool cmp(int x,int y){ return a[x]<a[y]; }
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]), id[i]=i;
    sort(id+1,id+n+1,cmp);
    for(int i=1;i<=n;i++) b[id[i]]=i;
    for(int i=1;i<=n;i++){
        root[i]=++cnt;
        update(1,n,cnt,root[i-1],b[i]);
    }
    while(m--){
        int x,y,k;
        scanf("%d%d%d",&x,&y,&k);
        printf("%d\n",a[id[query(1,n,root[x-1],root[y],k)]]);
    }
    return 0;
}
View Code

 

p.s.

1.突然想起来第二种用到二分的离散化方法当初是自己想出来的...虽然不是很好用,但NOIP以前学习和练习的时候一直用的是自己想的东西,lca的最朴素算法也是自己想的,后来发现和书上写得一模一样.现在离thusc和NOI近了,一直在想抓紧时间多学一点,这样的想法是没错的,要抓紧时间,不能太懒散,但是有时候未免太过功利,其实自己是明白自己基本没什么希望的,但是这毕竟是自己热爱的和想要做的事.就算最后什么奖都没有,一次次地打酱油又怎样呢?自己的实力确实差得很远,有梦想是对的,但不该太功利,我应该为自己能够继续追逐梦想而感到幸运.一直以来都不应该那样浮躁,静不下心来.我应该抓紧时间去享受自己的OI,用心去做自己想要做的事,趁自己还有机会.所以很重要的:不能放弃思考.

posted @ 2016-05-19 19:40  晴歌。  阅读(229)  评论(0编辑  收藏  举报