区间第K大

来源:互联网 发布:二木花花男的淘宝店 编辑:程序博客网 时间:2024/04/27 18:03

苟蒻某天突然被学长调戏bzoj1901区间第K大的题。。。

他说线段树可以做(哈哈哈哈哈哈)

于是莫名其妙就学会了这些东西写下这篇blog。。。。


--------------------------概念-------------------------


对于这类题,我想基础是掌握离散化的思想。。至于何为离散化?

苟蒻百度了一下,大概是化不可能为可能吧

首先是基础题,纯查找区间第k大


K-th Number
Time Limit: 20000MS Memory Limit: 65536KTotal Submissions: 43198 Accepted: 14253Case 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 31 5 2 6 3 7 42 5 34 4 11 7 3

Sample Output

563

Hint

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


嗯,poj2104。。。

先问个问题,如何用线段树的每个节点表示每个区间存在的数字数??

题目给的数字不超过10^9次方,但是如果企图直接扔进去(线段树点数大概是区间内数的数量的四倍吧)

嗯。。。当然动态开点是可以解决本问题的,但为了配合本题解法,此处引进离散化

考虑到最多只存在10^5个数字,我们将其排序,从小到大从1到n逐个标号,这样每个数就有个新的值

就是说原本我们需要10^9大小的线段树,但是其中大量空间是根本用不上的,而现在把需要的东西都拿出来

给予可行标号,这就是离散化吧(苟蒻本人理解。。)



然后对于本题,再引进一个叫做主席树的数据结构

我们对于某个长度为n的一位数组a,令其每个点都为一颗线段树,线段树的每个节点都表示为前i位数对应值域含有的元素个数

显然他们是可以相加减的,就像预处理前缀和一样

那么每个点都开一颗线段树,空间复杂度为O(n^2),肯定得炸。。

那么假设这么一张图为相邻两颗线段树



(土归土这不是重点)

可以发现,第二棵树是可以建在第一颗树上的!

于是对于每棵新树,需要新建的点仅为logn个,空间复杂度就省到了O(nlogn)



--------------------------解题-------------------------


对于每个[l,r]的询问,我们只需拿出a[l-1]和a[r]这两棵树

对于当前查找到的值域,计算出在其左边的元素个数,记为DT,

如果DT >= k,那么目标值在左子树否则右子数

不断二分离散化后的值域[1,cur],就是在这上面查找答案是在值域左半部分还是右半部分,O(logn)完成



#include<iostream>#include<cstdio>#include<algorithm>using namespace std;const int maxn = 1e5 + 10;struct Node{int l,r;int cnt;}pool[20*maxn];int a[maxn],sorta[maxn],n,m,i,j,d[maxn],cur = 1,tot = 0;void maintain(int o){pool[o].cnt = pool[pool[o].l].cnt + pool[pool[o].r].cnt;}int Build (int l,int r){int k = ++tot;if (l == r){pool[k].cnt = 0;return k;}int mid = (l+r) >> 1;    pool[k].l = Build (l,mid);    pool[k].r = Build (mid+1,r);    maintain(k);    return k;}int Insert (int o,int l,int r,int pos){int k = ++tot;pool[k] = pool[o];if (l == pos && r == pos){++pool[k].cnt;return k;}int mid = (l+r) >> 1;if (pos <= mid) pool[k].l = Insert(pool[o].l,l,mid,pos);else pool[k].r = Insert(pool[o].r,mid+1,r,pos);maintain(k);return k;}int query (int l,int r,int L,int R,int k){if (l == r) return l;int DT = pool[pool[R].l].cnt - pool[pool[L].l].cnt;int mid = (l+r) >> 1;if (DT >= k) return query(l,mid,pool[L].l,pool[R].l,k);else return query(mid+1,r,pool[L].r,pool[R].r,k-DT);}int main(){//freopen("yzy.txt","r",stdin);cin >> n >> m;for (i = 1; i <= n; i++){scanf("%d",&a[i]);sorta[i] = a[i];}sort (sorta + 1,sorta + n + 1);for (i = 2; i <= n; i++)  if (sorta[i] != sorta[i-1])    sorta[++cur] = sorta[i];d[0] = Build(1,cur);for (i = 1; i <= n; i++){int pos = lower_bound (sorta + 1,sorta + n + 1,a[i]) - sorta;d[i] = Insert (d[i-1],1,cur,pos);}while (m--){int x,y,k;scanf("%d%d%d",&x,&y,&k);int pos = query (1,cur,d[x-1],d[y],k);printf("%d\n",sorta[pos]);}return 0;}



--------------------------提升-------------------------


1901: Zju2112 Dynamic Rankings

Time Limit: 10 Sec  Memory Limit: 128 MB
Submit: 5625  Solved: 2337
[Submit][Status][Discuss]

Description

给定一个含有n个数的序列a[1],a[2],a[3]……a[n],程序必须回答这样的询问:对于给定的i,j,k,在a[i],a[i+1],a[i+2]……a[j]中第k小的数是多少(1≤k≤j-i+1),并且,你可以改变一些a[i]的值,改变后,程序还能针对改变后的a继续回答上面的问题。你需要编一个这样的程序,从输入文件中读入序列a,然后读入一系列的指令,包括询问指令和修改指令。对于每一个询问指令,你必须输出正确的回答。 第一行有两个正整数n(1≤n≤10000),m(1≤m≤10000)。分别表示序列的长度和指令的个数。第二行有n个数,表示a[1],a[2]……a[n],这些数都小于10^9。接下来的m行描述每条指令,每行的格式是下面两种格式中的一种。 Q i j k 或者 C i t Q i j k (i,j,k是数字,1≤i≤j≤n, 1≤k≤j-i+1)表示询问指令,询问a[i],a[i+1]……a[j]中第k小的数。C i t (1≤i≤n,0≤t≤10^9)表示把a[i]改变成为t。

Input

对于每一次询问,你都需要输出他的答案,每一个输出占单独的一行。

Output

Sample Input

5 3
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3

Sample Output

3
6

HINT

20%的数据中,m,n≤100; 40%的数据中,m,n≤1000; 100%的数据中,m,n≤10000。



嗯。。回到一开始被学长坑的那题,与poj2104相比,这题只是多了个修改(只是哈哈哈哈哈哈)

那么显然主席树行不通了,因为你一次要改一排。。。。修改的复杂度是O(n^2)于是你就炸掉了


这里加个小插曲:

  既然有修改数据,就是说一开始给你的那个数列的全部元素都可能在操作过程中改变,所以

如果一开始就离散化,得到的值域[1,cur]是不准确的。。咨询了下学长,被教导以离线设计的思路。。

何为离线?先读入所有操作,将以后修改用到的值也加入值域,这样就不怕因修改产生的某些错误啦


对于区间修改区间求和查询这类问题,自然联系到树状数组。。

对于树状数组,主席树貌似行不通(反正苟蒻不会。。。。。。。。。)

那么就让树状数组每个点都存一棵动态开点的线段树,修改新增节点最多O(logn)

每次要修改或插入的点也是O(logn)个

于是空间和时间复杂度都是O(nlog^2n)

具体实现吧。。。

应该不是很难


#include<algorithm>#include<iostream>#include<cstdio>using namespace std;const int maxn = 3e4 + 10;struct Node{int l,r,cnt;Node (){l = r = cnt = 0;}}pool[30*maxn];struct Q{int Type;int l,r,k;}query[maxn];int d[maxn],n,m,i,j,tot = 0,a[maxn],sorta[2*maxn],cur = 1;int ql[maxn],qr[maxn],sl,sr;char c[2];void maintain(int o){pool[o].cnt = pool[pool[o].l].cnt + pool[pool[o].r].cnt;}void Insert(int o,int l,int r,int pos){if (l == r){++pool[o].cnt;return;}int mid = (l+r) >> 1;if (pos <= mid){if (!pool[o].l) pool[o].l = ++tot;Insert(pool[o].l,l,mid,pos);}else {if (!pool[o].r) pool[o].r = ++tot;Insert(pool[o].r,mid+1,r,pos);}maintain(o);}void Modify(int o,int l,int r,int pos){if (l == r){--pool[o].cnt;return;}int mid = (l+r) >> 1;if (pos <= mid) Modify(pool[o].l,l,mid,pos);else Modify(pool[o].r,mid+1,r,pos);maintain(o);}int Query(int l,int r,int k){if (l == r) return l;int x,DT = 0;for (x = 1; x <= sl; x++) {if (!pool[ql[x]].l) pool[ql[x]].l = ++tot;DT -= pool[pool[ql[x]].l].cnt;}for (x = 1; x <= sr; x++){if (!pool[qr[x]].l) pool[qr[x]].l = ++tot;DT += pool[pool[qr[x]].l].cnt;}int mid = (l+r) >> 1;if (DT >= k) {for (x = 1; x <= sl; x++) ql[x] = pool[ql[x]].l;for (x = 1; x <= sr; x++) qr[x] = pool[qr[x]].l;return Query(l,mid,k);}else {for (x = 1; x <= sl; x++) ql[x] = pool[ql[x]].r;for (x = 1; x <= sr; x++) qr[x] = pool[qr[x]].r;return Query(mid+1,r,k-DT);}}int main(){//freopen("yzy.txt","r",stdin);cin >> n >> m;for (i = 1; i <= n; i++){scanf("%d",&a[i]);sorta[i] = a[i];}j = n;for (i = 1; i <= m; i++){scanf("%s",c);int x,y,z;if (c[0] == 'Q') {scanf("%d%d%d",&query[i].l,&query[i].r,&query[i].k);query[i].Type = 0;--query[i].l;}else {scanf("%d%d%d",&query[i].l,&query[i].r);sorta[++j] = query[i].r;query[i].Type = 1;}}sort (sorta + 1,sorta + j + 1);for (i = 2; i <= j; i++)  if (sorta[i] != sorta[i-1])    sorta[++cur] = sorta[i];for (i = 0; i <= n; i++) d[i] = ++tot;for (i = 1; i <= n; i++){int pos = lower_bound(sorta + 1,sorta + cur + 1,a[i]) - sorta;for (j = i; j <= n; j += j&-j)   Insert(d[j],1,cur,pos); }for (i = 1; i <= m; i++){if (query[i].Type){int pos1 = lower_bound(sorta + 1,sorta + cur + 1,query[i].r) - sorta;int pos2 = lower_bound(sorta + 1,sorta + cur + 1,a[query[i].l]) - sorta;for (j = query[i].l; j <= n; j += j&-j) Modify(d[j],1,cur,pos2);for (j = query[i].l; j <= n; j += j&-j) Insert(d[j],1,cur,pos1);a[query[i].l] = query[i].r;}else {sl = sr = 0;for (j = query[i].l; j > 0; j -= j&-j) ql[++sl] = d[j];for (j = query[i].r; j > 0; j -= j&-j) qr[++sr] = d[j];int pos = Query(1,cur,query[i].k);printf("%d\n",sorta[pos]);}}return 0;}


0 0
原创粉丝点击