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 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 10 9 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.

题解:

以前用主席树写过,现在学习了一下划分树。

划分树,类似线段树,主要用于求解某个区间的第k 大元素(时间复杂度log(n)),快排本也可以快速找出,但快排会改变原序列,所以每求一次都得恢复序列。

  下面就以 POJ 2104 进行解说:

  题目意思就是,给你n 个数的原序列,有m 次询问,每次询问给出l、r、k,求原序列l 到r 之间第k 大的数。n范围10万,m范围5千,这道题用快排也可以过,快排过的时间复杂度n*m,而划分树是m*logn(实际上应该是nlogn才对,因为建图时间是nlogn,n又比m大),分别AC后,时间相差很明显。

  划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序。见下图:

   

  红点标记的就是进入左孩子的元素。

  当然,一般不会说每个结点开个数组存数,经观察,每一层都包含原本的n 个数,只是顺序不同而已,所以我们可以开val[20][N]来保存,也就是说共20层,每一层N个数。

  我们还需要一个辅助数组num,num[i]表示i 前面有多少数进入左孩子(i 和i 前面可以弄成本结点内也可以是所有,两种风格不同而已,下面采取的是本结点内),和val一样,num也开成num[20][N],来表示每一层,i 和i 前面(本结点)有多少进入左孩子。

  第一层:1 进入左孩子,num[1]=1,5 进入右孩子,num[2]=1,...,num[8]=4。

  第二层:5 进入左孩子,num[5]=1,6 进入右孩子,num[6]=1,...,num[8]=2。

  建图时就是维护每一层val[]和num[]的值就可以了。

很是清晰的,

最后那个询问其实不难,自己一开始瞎比比了一会,卡了许久时间。

    这样子转换一下,多仔细考虑转换l,r那一段。

 1 #include<cstring>
 2 #include<cmath>
 3 #include<iostream>
 4 #include<algorithm>
 5 #include<cstdio>
 6
 7 #define N 100007
 8 using namespace std;
 9 inline int read()
10 {
11     int x=0,f=1;char ch=getchar();
12     while(ch<‘0‘||ch>‘9‘){if (ch==‘-‘)f=-1;ch=getchar();}
13     while(ch>=‘0‘&&ch<=‘9‘){x=(x<<3)+(x<<1)+ch-‘0‘;ch=getchar();}
14     return x*f;
15 }
16
17 int n,m;
18 int a[N],val[21][N],num[21][N];
19
20 void build(int deep,int l,int r)
21 {
22     if (l==r) return;
23     int mid=(l+r)>>1,same=mid-l+1;
24     for (int i=l;i<=r;i++)
25         if (val[deep][i]<a[mid]) same--;
26     int lh=l,rh=mid+1;
27     for (int i=l;i<=r;i++)
28     {
29         if (i==l) num[deep][i]=0;
30         else num[deep][i]=num[deep][i-1];
31         if (val[deep][i]<a[mid] || val[deep][i]==a[mid]&&same>0)//没有same那么所以有和a[mid]一样大的树都会进入做子树
32         {
33             val[deep+1][lh++]=val[deep][i];
34             num[deep][i]++;
35             if (val[deep][i]==a[mid]) same--;
36         }
37         else val[deep+1][rh++]=val[deep][i];
38     }
39     build(deep+1,l,mid),build(deep+1,mid+1,r);
40 }
41 int query(int deep,int l,int r,int x,int y,int k)
42 {
43     if (l==r) return val[deep][l];
44     int ly,mid=(l+r)>>1;
45     if (x==l) ly=0;
46     else ly=num[deep][x-1];
47     int sum=num[deep][y]-ly;//表示放在左边有多少。
48     if (sum>=k) return query(deep+1,l,mid,l+ly,l+num[deep][y]-1,k);//转换到这一段区间放入做子树的那一段。
49     else
50     {
51         int lr=mid+1+(x-l-ly);
52         return query(deep+1,mid+1,r,lr,lr+y-x+1-sum-1,k-sum);
53     }
54 }
55 int main()
56 {
57     while(~scanf("%d%d",&n,&m))
58     {
59         for (int i=1;i<=n;i++)
60             a[i]=read(),val[1][i]=a[i];
61         sort(a+1,a+n+1);
62         build(1,1,n);
63         for (int i=1;i<=m;i++)
64         {
65             int l=read(),r=read(),k=read();
66             printf("%d\n",query(1,1,n,l,r,k));
67         }
68     }
69 }
时间: 2024-10-11 03:54:45

K-th Number POJ - 2104 划分树的相关文章

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

poj 2401 划分树 求区间第k大的数

题目:http://poj.org/problem?id=2104 划分树待我好好理解下再写个教程吧,觉得网上的内容一般,,, 模板题: 贴代码: #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define CLR(a) memset(a,0,sizeof(a)) const int MAXN = 1000

K-th Number POJ - 2104 (整体二分)

K-th Number POJ - 2104 之前学主席树写了一遍 最近再看CDQ分治和整体二分,一直不是很理解,看着别人代码稍微理解了一些 1 //比主席树慢了挺多 2 #include <iostream> 3 #include <cstring> 4 #include <cstdio> 5 6 using namespace std; 7 8 const int maxn = 1e5 + 10; 9 const int maxq = 5010; 10 const

hdu 2665 Kth number (poj 2104 K-th Number) 划分树

划分树的基本功能是,对一个给定的数组,求区间[l,r]内的第k大(小)数. 划分树的基本思想是分治,每次查询复杂度为O(log(n)),n是数组规模. 具体原理见http://baike.baidu.com/link?url=vIUKtsKYx7byeS2KCOHUI14bt_0sdHAa9BA1VceHdGsTv5jVq36SfZgBKdaHYUGqIGvIGrE_aJtqy0D0b1fCoq 个人感觉看代码是最好的学习方法. #include <cstdio> #include <c

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大

题目链接: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 主席树

附上题目链接: 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],记录在

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