首页 > 代码库 > cogs930.[河南省队2012] 找第k小的数

cogs930.[河南省队2012] 找第k小的数

930. [河南省队2012] 找第k小的数

★★★   输入文件:kth.in   输出文件:kth.out   简单对比
时间限制:1 s   内存限制:128 MB

题目描述

看到很短的题目会让人心情愉悦,所以给出一个长度为N的序列A1,A2,A3,...,AN,

现在有M个询问,每个询问都是Ai...Aj中第k小的数等于多少。


输入格式

第一行两个正整数N,M。
第二行N个数,表示序列A1,A2,...,AN。
紧着的M行,每行三个正整数i,j,k(k≤j-i+1),表示

询问Ai...Aj中第k小的数等于多少。


输出格式

共输出M行,第i行输出第i个询问的答案。


样例输入1:

4 3

4 1 2 3

1 3 1

2 4 3

1 4 4


样例输出1:
1
3
4

样例输入2:
5 5
4 2 9 9 10
1 3 1
2 4 3
1 4 4
3 5 2
2 5 2

样例输出2:
2
9
9
9
9

注释:
询问区间的第k小值并非严格第k小,例如样例2中第4个询问,询问3到5中第2小的数,
答案输出9,并不是严格第2小的10。

数据范围:
在50%的数据中,1<=N<=10000,1<=M<=10000,A[i]<=100000;
在100%的数据中,1<=N<=100000,1<=M<=100000,A[i]<=1000000;

[题解]

主席树经典应用,找区间第k小的数.

我做这题是在学treap之前学的,现在想想如果先学了treap应该写这题会非常简单吧...

我是想了两节课的做法....

其实这题的思想就是运用treap找第k小的数的思想;

回到正题: 主席树其实可以当做一个二维数据结构;(自己的理解)

以每个新建的线段树为一维,维护的信息为第二维

那么我们离散化,然后对数据进行排序

线段树维护排过序的数组中区间的数据个数(假如在1 号位那就是最小的数据)或者这样说,每个叶子节点对应的就是排序数组中的某个位置(位置可以二分查找快速找到);

然后対每个点在上一个线段树的基础上建立新的线段树(上一个线段树数据迁移过来了);

运用的思想是treap找k小数的思想,但是这题是区间k小,而treap的可视为1-n区间

若求[i-j]区间第k小数

那么就用第i-1棵线段树与第j棵进行操作;

这个区间第k小的数一定存在(默认)

查询操作同时在第i-1棵线段树和j棵线段树进行;(第i-1棵线段树维护的是1到i-1数据的信息,j同理)

那么到某一个区间时用第j棵的区间总数-第i-1棵的区间总数其实就是当前区间的数据总数;

那么就把区间的信息求出来了,此时转化为treap求k小数;

因为是排过序的

左儿子的数都比右儿子的小;

如果左儿子数据总数比k大那么k小数一定在左儿子,

否则,一定在右儿子,而且是第k-左儿子数据总数  小的数;

(我的写法不是由小到大排序的道理相同)

#include <iostream>
#include <cstring>
#include <cmath>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <cstdio>
#include <time.h>
#include <map>
#include <deque>
#include <string>
using namespace std;
int n,t;
int ppp[100010];
int data[100010];
int hash[100100];
int tot;
int ls[5000010];
int rs[5000010];
int sum[5000010];
int gen[5000010];
int dian;
inline bool cmp(int a,int b)
{
    return a>b;
}
inline int erfen(int x)
{
    int l=1;int r=tot;
    while(l+1<r)
    {
        int m=(l+r)/2;
        if(x<hash[m])l=m+1;
        else r=m;
    }
    if(hash[l]==x)return l;
    return r;
}
inline void gai(int shang,int &xin,int l,int r,int nl,int nr)
{
    xin=++dian;
    if(l>=nl&&r<=nr)
    {
        sum[xin]=sum[shang]+1;
        return ;
    }
    ls[xin]=ls[shang];
    rs[xin]=rs[shang];
    int m=(l+r)>>1;
    if(m>=nl)
    {
        gai(ls[shang],ls[xin],l,m,nl,nr);
    }
    if(m<nr)
    {
        gai(rs[shang],rs[xin],m+1,r,nl,nr);
    }
    sum[xin]=sum[ls[xin]]+sum[rs[xin]];
}
inline int find(int shang,int xin,int l,int r,int k)
{
    if(l==r)
    {
        return l;
    }
    //int zuo=sum[ls[shang]]-sum[ls[xin]];
    int you=sum[rs[xin]]-sum[rs[shang]];
    int m=(l+r)>>1;
    if(you>=k)
    {
        return find(rs[shang],rs[xin],m+1,r,k);
    }
    else
    {
        return find(ls[shang],ls[xin],l,m,k-you);
    }
}
int main()
{
    freopen("kth.in","r",stdin);
    freopen("kth.out","w",stdout);
    ios::sync_with_stdio(false);
    scanf("%d%d",&n,&t);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&ppp[i]);
        data[i]=ppp[i];
    }
    sort(ppp+1,ppp+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        if(ppp[i]!=ppp[i-1])
        {
            hash[++tot]=ppp[i];
        }
    }
    for(int i=1;i<=n;i++)
    {
        int u=erfen(data[i]);
        gai(gen[i-1],gen[i],1,tot,u,u);
    }
    while(t--)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        printf("%d\n",hash[find(gen[x-1],gen[y],1,tot,z)]);
        //cout<<x<<y<<z<<" "<<find(gen[x-1],gen[y],1,tot,z)<<endl;
    }
    return 0;
}
欢迎指错,或者改进,我的表达可能不太好.

 

cogs930.[河南省队2012] 找第k小的数