苟蒻某天突然被学长调戏bzoj1901区间第K大的题。。。
他说线段树可以做(哈哈哈哈哈哈)
于是莫名其妙就学会了这些东西写下这篇blog。。。。
--------------------------概念-------------------------
对于这类题,我想基础是掌握离散化的思想。。至于何为离散化?
苟蒻百度了一下,大概是化不可能为可能吧
首先是基础题,纯查找区间第k大
Time Limit: 20000MS | Memory Limit: 65536K | |
Total Submissions: 43198 | Accepted: 14253 | |
Case Time Limit: 2000MS |
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 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
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
嗯,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;
}