题意: 求区间第K大
划分树
划分树好像只能用于求这个
划分树,顾名思义是将n 个数的序列不断划分,根结点就是原序列,左孩子保存父结点所有元素排序后的一半,右孩子也存一半,也就是说排名1 -> mid的存在左边,排名(mid+1) -> r 的存在右边,同一结点上每个元素保持原序列中相对的顺序。
同时,用一个标记数组记录,每次划分时,当前位置累积被分配到左子树的个数,查询时就判断该区间第k大是被划分到了左子树,还是右子树,直到找到。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
const int maxn = 100050;
using namespace std;
int tree[20][maxn];
int tsort[maxn];
int toleft[20][maxn];
int n, m;
void build(int l, int r, int deep){
if(l == r) //到达叶子
return ;
int m = (l+r)>>1;
int same = m - l + 1; //得到应该被划分到左子树的个数 但same是用来表示,与中位数相等但是应该被划分去左子树的个数
for(int i=l; i<=r; i++) //求出真正的same
if(tree[deep][i] < tsort[m])
same--;
int lpos=l, rpos=m+1; //左右子树的起点
for(int i=l; i<=r; i++){
if(tree[deep][i]<tsort[m]) //划分到左子树
tree[deep+1][lpos++] = tree[deep][i];
else{
if(tree[deep][i]==tsort[m] && same) //与中位数相等,但是要划分到左子树
tree[deep+1][lpos++] = tree[deep][i], same--;
else
tree[deep+1][rpos++] = tree[deep][i]; //划分右子树
}
toleft[deep][i] = toleft[deep][l-1]+(lpos-l); //计算当前位置划分到左子树的个数
}
build(l, m, deep+1);
build(m+1, r, deep+1);
}
int query(int l, int r, int deep, int L, int R, int k){
//printf("%d %d %d %d %d****\n", l, r, L, R, k);
if(l == r)
return tree[deep][l];
int m = (l+r)>>1;
int cnt = toleft[deep][R] - toleft[deep][L-1]; //被划分到左子树的个数
//printf("%d %d###\n", m, cnt);
//newL newR newk 是被划分到左子树(或右子树),在子树中的新区间
if(cnt>=k){
int newL = l + toleft[deep][L-1] - toleft[deep][l-1];
int newR = newL + cnt - 1;
return query(l, m, deep+1, newL, newR, k);
}
else{
int newR=R+toleft[deep][r]-toleft[deep][R];
int newL=newR-(R-L-cnt);
int newk = k-cnt;
return query(m+1, r, deep+1, newL, newR, newk);
}
}
int main()
{
int t;
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
scanf("%d", &tree[0][i]);
tsort[i] = tree[0][i];
}
sort(tsort+1, tsort+n+1);
build(1, n, 0);
/*
for(int i=0; i<=3; i++){
for(int j=1; j<=n; j++){
printf("%d ", tree[i][j]);
}
printf("******\n");
}
printf("\n\n\n\n");
for(int i=0; i<=3; i++){
for(int j=1; j<=n; j++){
printf("%d ", toleft[i][j]);
}
printf("******\n");
}
*/
//printf("####\n");
while(m--){
int a, b, k;
scanf("%d%d%d", &a, &b, &k);
printf("%d\n", query(1, n, 0, a, b, k));
}
}
return 0;
}
/*
7 3
1 5 2 6 3 7 4
2 5 3
4 4 1
1 7 3
*/
主席树
#include <bits/stdc++.h>
const int maxn = 100050;
using namespace std;
typedef long long ll;
int a[maxn], b[maxn], rt[maxn*20], ls[maxn*20], rs[maxn*20], sum[maxn*20];
// rt[i] 第i个版本的根节点所在的位置
// ls[i] rs[i] 节点的左右子树的位置
int n, m, tot, len;
void build(int &o, int l, int r){ //确定最初的版本
o = ++tot; //这里 tot 的统计是用的dfs(先序)
sum[o] = 0;
if(l == r)
return ;
int m = (l+r)>>1;
build(ls[o], l, m);
build(rs[o], m+1, r);
}
void update(int &o, int l, int r, int last, int p){
o = tot++;
ls[o] = ls[last];
rs[o] = rs[last];//先继承前个版本的左右子树
sum[o] = sum[last]+1;
if(l == r)
return ;
int m = (l+r)>>1;
if(p<=m) //更新左右子树
update(ls[o], l, m, ls[last], p);
else
update(rs[o], m+1, r, rs[last], p);
}
int query(int ss, int tt, int l, int r, int k){
if(l == r)
return l;
int m = (l+r)>>1;
int cnt = sum[ls[tt]] - sum[ls[ss]]; //两个版本中左子树的相差的个数
if(cnt >= k) //答案在左子树中
return query(ls[ss], ls[tt], l, m, k);
else //答案在右子树中
return query(rs[ss], rs[tt], m+1, r, k-cnt);
}
int main()
{
int t;
scanf("%d", &t);
while(t--){
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
scanf("%d", &a[i]);
b[i] = a[i];
}
sort(b+1, b+n+1);
tot = 0;
len = unique(b+1, b+n+1) - (b+1); //要离散化的去重数组
build(rt[0], 1, len);
for(int i=1; i<=n; i++){
a[i] = lower_bound(b+1, b+len+1, a[i]) - b;
update(rt[i], 1, len, rt[i-1], a[i]);
}
while(m--){
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
int ans = query(rt[l-1], rt[r], 1, len, k);
printf("%d\n", b[ans]);
}
}
return 0;
}
/*
* 在这里主席树其实相当于多颗权值线段树
* 由于每次版本的更新只会更新一个点,所以对于每个点,只会有一个子节点会发生更新
* 主席树就是重复利用了不会更新的节点,只去创建会更新的节点
*/