静态区间第K大
题目链接:http://poj.org/problem?id=2104
Time Limit: 20000MS | Memory Limit: 65536K | |
Total Submissions: 64800 | Accepted: 22822 | |
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
code:
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 2e6+5;
const int mxb = 1e5+5;
struct node{
int l,r,sum;
int ch[2];
}tr[maxn];
int tot;
int sz;
int a[mxb];
int b[mxb];
int rt[mxb];
void build(int &x,int L,int R){
x = ++ tot;
tr[x].l=L;
tr[x].r=R;
if(L==R) return ;
int M=(L+R)>>1;
build(tr[x].ch[0],L,M);
build(tr[x].ch[1],M+1,R);
}
void insert(int pre,int &x,int pos){
x = ++ tot;
tr[x]=tr[pre];
tr[x].sum++;
if(tr[x].l==tr[x].r) return ;
int m=(tr[x].l+tr[x].r)>>1;
if(pos<=m) insert(tr[pre].ch[0],tr[x].ch[0],pos);
else insert(tr[pre].ch[1],tr[x].ch[1],pos);
}
int query(int pre,int x,int k){
if(tr[x].l==tr[x].r) {
return a[tr[x].l];
}
int rs=tr[tr[x].ch[0]].sum-tr[tr[pre].ch[0]].sum;
if(rs>=k) return query(tr[pre].ch[0],tr[x].ch[0],k);
else return query(tr[pre].ch[1],tr[x].ch[1],k-rs);
}
int main(){
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(a+1,a+1+n);
sz = unique(a+1,a+n+1)-(a+1);
build(rt[0],1,sz);
for(int i=1;i<=n;i++){
int pos=lower_bound(a+1,a+sz+1,b[i])-a;
insert(rt[i-1],rt[i],pos);
}
while(q--){
int l,r,k;
scanf("%d%d%d",&l,&r,&k);
int ans=query(rt[l-1],rt[r],k);
printf("%d\n",ans);
}
return 0;
}
动态区间第K大
题目链接:http://www.joyoi.cn/problem/tyvj-3070
题目大意:
题目限制
时间限制 | 内存限制 | 评测方式 | 题目来源 |
1000ms | 65536KiB | 标准比较器 | Local |
题目描述
给定一个长度为N的已知序列Ai,要求维护这个序列,能够支持以下两种操作:
1、查询A[i],A[i+1],A[i+2],...,Aj中,升序排列后排名第k的数。
2、修改A[i]的值为j。
所谓排名第k,指一些数按照升序排列后,第k位的数。例如序列{6,1,9,6,6},排名第3的数是6,排名第5的数是9。
输入格式
第一行包含一个整数D(0<=D<=4),表示测试数据的数目。接下来有D组测试数据,每组测试数据中,首先是两个整数N( 1 <= N <= 50000 ),M( 1 <= M <= 10000 ),表示序列的长度为N,有M个操作。接下来的N个不大于1,000,000,000正整数,第i个表示序列A[i]的初始值。然后的M行,每行为一个操作
Q i j k 或者
C i j
分别表示查询A[i],A[i+1],A[i+2],...,Aj中,升序排列后排名第k的数,和修改A[i]的值为j。
输出格式
对于每个查询,输出一行整数,为查询的结果。测试数据之间不应有空行。
sol:
用树状数组处理前缀和。
有个优化,先建好原树,然后新建一个修改树,所有的更新都在修改树上进行,计算的时候加上修改树的值。
code:
#include<iostream>
#include<stdio.h>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 6e4 + 5;
struct node {
int ch[2];
int sum;
};
node tr[maxn << 5];
int tot, top, sz, sl, sr;
int a[maxn], b[maxn];
int L[maxn], R[maxn];
int rt[2][maxn];
int op[maxn][3];
int n, q;
bool flag[maxn];
int inline lowbit(int x) { return x&(-x); }
void update( int pre, int l, int r, int &x, int pos, int o) {
x = ++ tot;
tr[x] = tr[pre];
tr[x].sum += o;
if (l == r) return;
int m = (l + r) >> 1;
if (pos <= m) update(tr[pre].ch[0], l, m, tr[x].ch[0], pos, o);
else update(tr[pre].ch[1], m + 1, r, tr[x].ch[1], pos, o);
}
void build(int &x, int l, int r) {
x = ++tot;
tr[x].sum = 0;
if (l == r) return;
int mid = (l + r) >> 1;
build(tr[x].ch[0], l, mid);
build(tr[x].ch[1], mid + 1, r);
}
int query(int l, int r, int k) {
l--;
for (int j = l; j > 0; j -= lowbit(j)) L[++sl] = rt[1][j];
for (int j = r; j > 0; j -= lowbit(j)) R[++sr] = rt[1][j];
int lx = 1, rx = sz;
int Tl = rt[0][l];
int Tr = rt[0][r];
while (lx < rx) {
int mid = (lx + rx) >> 1 ;
int tmp = tr[tr[Tr].ch[0]].sum - tr[tr[Tl].ch[0]].sum;
for (int i = 1; i <= sl; i++) tmp -= tr[tr[L[i]].ch[0]].sum;
for (int i = 1; i <= sr; i++) tmp += tr[tr[R[i]].ch[0]].sum;
if (k <= tmp) {
rx = mid;
for (int i = 1; i <= sl; i++) L[i] = tr[L[i]].ch[0];
for (int i = 1; i <= sr; i++) R[i] = tr[R[i]].ch[0];
Tl = tr[Tl].ch[0];
Tr = tr[Tr].ch[0];
}
else {
lx = mid + 1;
k -= tmp;
for (int i = 1; i <= sl; i++) L[i] = tr[L[i]].ch[1];
for (int i = 1; i <= sr; i++) R[i] = tr[R[i]].ch[1];
Tl = tr[Tl].ch[1];
Tr = tr[Tr].ch[1];
}
}
return lx;
}
void init() {
tot = sz = sl = sr = top = 0;
}
int inline Pos(int x) {
return lower_bound(b + 1, b + sz + 1, x) - b;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
init();
scanf("%d%d", &n, &q);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
b[++top] = a[i];
}
char s[3];
for (int i = 1; i <= q; i++) {
scanf("%s", s);
scanf("%d%d", &op[i][0], &op[i][1]);
if (s[0] == 'Q') {
scanf("%d", &op[i][2]);
flag[i] = true;
}
else {
b[++top] = op[i][1];
flag[i] = false;
}
}
sort(b + 1, b + top + 1);
sz = unique(b + 1, b + top + 1) - (b + 1);
build(rt[0][0], 1, sz);
for (int i = 1; i <= n; i++) rt[1][i] = rt[0][0];
for (int i = 1; i <= n; i++) {
update(rt[0][i - 1], 1, sz, rt[0][i], Pos(a[i]), 1);
}
for (int i = 1; i <= q; i++) {
if (flag[i]) {
sl = sr = 0;
int ans = query(op[i][0], op[i][1], op[i][2]);
printf("%d\n", b[ans]);
}
else{
int p_i = op[i][0];
for (int j = p_i; j <= n; j += lowbit(j)) {
int p_r = rt[1][j];
update(p_r, 1, sz, rt[1][j], Pos(a[p_i]), -1);
}
a[p_i] = op[i][1];
for (int j = p_i; j <= n; j += lowbit(j)) {
int p_r = rt[1][j];
update(p_r, 1, sz, rt[1][j], Pos(a[p_i]), 1);
}
}
}
}
return 0;
}
最后小结一下:主席树为序列所有的前缀维护一颗线段树 。各个线段树维护的是对应权值区间的数出现的个数(根结点维护1到最大权值的数出现次数),如果数据范围较大则需要离散化,这样就需要离线处理。于是对于区间(l,r)第k大的查询,求出前l-1区间的结果suml和前r区间的结果sumr,两者相减就是(l,r)区间在对应权值范围的个数,递归处理即可。注意到每次修改值只会影响上颗线段树上从修改权值叶子到根的一条链,其余结点都可以重用。同理,可持久化线段树就是复用上个版本的结点。