POJ2104 K-th Number[主席树]【学习笔记】

K-th Number
Time Limit: 20000MS   Memory Limit: 65536K
Total Submissions: 51440   Accepted: 17594
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

document:
1.http://blog.csdn.net/metalseed/article/details/8045038
2.http://www.cnblogs.com/oyking/p/3230296.html
 
解决区间极值查询问题
主席树就是对每个前缀做了一个线段树
这样相当于处理了前缀和
提取一段区间,就是T[j]-T[i-1]
每棵线段树都按照[1,n]建树(保证链可以共用)
离散化后按照序列的顺序建树
T[i+1]和T[i]只有一条链不同,其他的可以共用
 
插入操作就是只把新链上的节点新建,其他的都用上一课树的
查询操作就是一个kth的过程,只不过要用两棵树的差值
 
PS:
1.两种建树写法,貌似用引用比较快
2.每个数字不同,离散化排序就行了
 
[2016-12-31]一些新理解
就是可持久化线段树对于每个版本建立一颗线段树,可以查询历史版本
为了节省内存和时间直接使用历史版本的形态,把修改的地方沿途新开节点,其他地方继承历史版本
主席树就是前缀和套线段树,每个前缀和建立一颗线段树,继承上一个历史版本,只是单点修改
区间修改也一样,所有区间修改到的点都要新开节点 标记下放时也要新开
总结:就是把各种操作修改到(包括因为下方标记而修改)的节点新开节点,写法上就是多了新开节点而已,其他一样
主席树的线段树是值域线段树,线段树的形态固定并且维护的信息是出现次数所以是可减的,那么主席树做差就得到了一个区间的值域线段树,可以在线段树上二分求kth
 
主席树的空间实际<理论
 
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=1e5+5;
int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,m,mp[N],l,r,k;
struct data{
    int v,id;
    bool operator <(const data &r)const{return v<r.v;}
}a[N];
struct node{
    int lc,rc,size;
}t[N*20];
int cnt=0,root[N];
void insert(int num,int &x,int l,int r){//printf("ins %d %d %d\n",l,r,x);
    cnt++;
    t[cnt]=t[x];x=cnt;
    ++t[x].size;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(num<=mid) insert(num,t[x].lc,l,mid);
    else insert(num,t[x].rc,mid+1,r);
}
int ins(int num,int pre,int l,int r){
    int x=++cnt;
    t[x]=t[pre]; ++t[x].size;
    if(l==r) return x;
    int mid=(l+r)>>1;
    if(num<=mid) t[x].lc=ins(num,t[x].lc,l,mid);
    else t[x].rc=ins(num,t[x].rc,mid+1,r);
    return x;
}
int query(int i,int j,int l,int r,int k){
    if(l==r) return l;
    int ls=t[t[j].lc].size-t[t[i].lc].size;
    int mid=(l+r)>>1;
    if(k<=ls) return query(t[i].lc,t[j].lc,l,mid,k);
    else return query(t[i].rc,t[j].rc,mid+1,r,k-ls);
}

int main(){
    n=read();m=read();
    for(int i=1;i<=n;i++) a[i].v=read(),a[i].id=i;
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) mp[a[i].id]=i;
    
    for(int i=1;i<=n;i++){
        //root[i]=ins(mp[i],root[i-1],1,n);
        root[i]=root[i-1];
        insert(mp[i],root[i],1,n);
    }
    while(m--){
        l=read();r=read();k=read();
        printf("%d\n",a[query(root[l-1],root[r],1,n,k)].v);
        
    }
}
View Code

 

[2017-03-02]

在扔上一点课件上的东西,虽然感觉跟前面的总结有点重复,但还是有点用吧

函数式编程:不修改,只新增(保留所有的历史版本)。
@ 考虑一次单点修改对整棵树的信息的影响;只有一条链上的
信息真正改变了。
@ 函数式线段树:对于线段树所有的单点修改操作,不真正的
修改,而是通过新增节点的方式来构建。

由于除了链上的信息是不变的,所以把直接指向它们就可以
了。
@ 所以整棵线段树是动态的,要用动态的节点来实现。
@ 只需要记录根节点,就能访问“第x 次修改后的线段树”
了。

新模板:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define lc(x) t[x].lc
#define rc(x) t[x].rc
typedef long long ll;
const int N=1e5+5;
int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1; c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0'; c=getchar();}
    return x*f;
}
int n,m,mp[N],l,r,k;
struct Fode{
    int v,id;
    bool operator <(const data &r)const{return v<r.v;}
}a[N];
struct node{
    int lc,rc,size;
}t[N*20];
int sz,root[N];
void fIns(int &x,int l,int r,int p){
    t[++sz]=t[x];x=sz;
    t[x].size++;
    if(l==r) return;
    int mid=(l+r)>>1;
    if(p<=mid) fIns(t[x].lc,l,mid,p);
    else fIns(t[x].rc,mid+1,r,p);
}
int fQue(int x,int y,int l,int r,int k){
    if(l==r) return l;
    int lsize=t[lc(y)].size-t[lc(x)].size;
    int mid=(l+r)>>1;
    if(k<=lsize) return fQue(lc(x),lc(y),l,mid,k);
    else return fQue(rc(x),rc(y),mid+1,r,k-lsize);
}

int main(){
    freopen("in","r",stdin);
    n=read();m=read();
    for(int i=1;i<=n;i++) a[i].v=read(),a[i].id=i;
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++) mp[a[i].id]=i;
    
    for(int i=1;i<=n;i++) root[i]=root[i-1],fIns(root[i],1,n,mp[i]);
    while(m--){
        l=read();r=read();k=read();
        printf("%d\n",a[fQue(root[l-1],root[r],1,n,k)].v);
    }
}

 

 
 
posted @ 2016-12-11 22:55  Candy?  阅读(729)  评论(0编辑  收藏  举报