区间第K大
来源:互联网 发布:二木花花男的淘宝店 编辑:程序博客网 时间:2024/04/27 18:03
苟蒻某天突然被学长调戏bzoj1901区间第K大的题。。。
他说线段树可以做(哈哈哈哈哈哈)
于是莫名其妙就学会了这些东西写下这篇blog。。。。
--------------------------概念-------------------------
对于这类题,我想基础是掌握离散化的思想。。至于何为离散化?
苟蒻百度了一下,大概是化不可能为可能吧
首先是基础题,纯查找区间第k大
Description
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 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
Sample Input
7 31 5 2 6 3 7 42 5 34 4 11 7 3
Sample Output
563
Hint
嗯,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 MBSubmit: 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
3 2 1 4 7
Q 1 4 3
C 2 6
Q 2 5 3
Sample Output
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;}
- 区间第K大
- 第K大区间
- 区间第k大
- 静态区间第K大
- poj2104 区间第K大
- 区间第K大值
- 区间第K大(划分树)
- ZOJ2112(区间动态求第K大)
- 区间第k大(poj 2104)
- 带修改的区间第k大
- poj2104(区间第k大+离散化)
- 线段树求解区间第k大
- 51nod1686 第K大区间
- 51nod 第K大区间
- 51nod 1686 第k大区间
- 二分 51Nod1686 第K大区间
- 51nod1685:第K大区间2
- 51nod1685 第K大区间2
- Intent
- gem install 出现Errno::ECONNRESET: Connection reset by peer - SSL_connect
- 写书是怎样的经历
- /var/lock/cinder不存在导致openstack的kilo版本删除云硬盘一直处于deleteing中问题处理。
- LeetCode----Ugly NumberII
- 区间第K大
- 使用 Sublime开发 Jade
- iOS中Objective-C与JavaScript之间相互调用的实现
- 堆和栈的区别
- Android四大组件Broadcast Receiver详解
- 简明深度学习方法概述 Deep Learning:Methods and Application
- 压电陶瓷材料的主要性能及参数
- Unity_事件函数
- 单链表找环