[bzoj1901][zoj2112][Dynamic Rankings] (整体二分+树状数组 or 动态开点线段树 or 主席树)

时间:2023-03-08 18:29:43
[bzoj1901][zoj2112][Dynamic Rankings] (整体二分+树状数组 or 动态开点线段树 or 主席树)

Dynamic Rankings


Time Limit: 10 Seconds      Memory Limit: 32768 KB

The Company Dynamic Rankings has developed a new kind of computer that is no longer satisfied with the query like to simply find the k-th smallest number of the given N numbers. They have developed a more powerful system such that for N numbers a[1], a[2], ..., a[N], you can ask it like: what is the k-th smallest number of a[i], a[i+1], ..., a[j]? (For some i<=j, 0<k<=j+1-i that you have given to it). More powerful, you can even change the value of some a[i], and continue to query, all the same.

Your task is to write a program for this computer, which

- Reads N numbers from the input (1 <= N <= 50,000)

- Processes M instructions of the input (1 <= M <= 10,000). These instructions
include querying the k-th smallest number of a[i], a[i+1], ..., a[j] and change
some a[i] to t.

Input

The first line of the input is a single number X (0 < X <= 4), the number
of the test cases of the input. Then X blocks each represent a single test case.

The first line of each block contains two integers N and M, representing N numbers
and M instruction. It is followed by N lines. The (i+1)-th line represents the
number a[i]. Then M lines that is in the following format

Q i j k or
C i t

It represents to query the k-th number of a[i], a[i+1], ..., a[j] and change
some a[i] to t, respectively. It is guaranteed that at any time of the operation.
Any number a[i] is a non-negative integer that is less than 1,000,000,000.

There're NO breakline between two continuous test cases.

Output

For each querying operation, output one integer to represent the result. (i.e.
the k-th smallest number of a[i], a[i+1],..., a[j])

There're NO breakline between two continuous test cases.

Sample Input


Q
C
Q Q
C
Q

Sample Output


Solution

算是整体二分的经典题目吧

大概是带修改的求区间第k小

这里用离线做法

首先,在数据范围较大的情况下要对于所有出现在输入中的区间数字进行离散化

之后,存下所有的询问,将修改视为2操作,赋值操作视为1操作,3操作则是询问

将离散化后的数字区间拿来做二分

大致思路是这样的:

1.确定答案范围[L,R],mid=L+R>>1;
2.算出答案在[L,mid]内的操作对答案在[mid+1,R]内的操作的贡献;
3.将答案在[L,mid]内的操作放入队列Q1,solve(Q1,L,mid)
将答案在[mid+1,R]内的操作放入Q2,solve(Q2,mid+1,R)

由于询问和操作是按时间顺序处理的,为了满足二分性质还需对询问和操作的区间进行排序,这里用到一点树状数组求逆序对的思想,我们用树状数组维护题目中虚拟的那个区间中的n个数的区间,用树状数组统计区间中询问和操作的贡献,用差分进行区间修改。

之后对每个询问和操作进行分治,分到两个区间中分别处理

每当遇到当前区间已经缩小成为点时,求记录答案

最后按要求输出即可

#include<map>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int M=;
inline int read(){
int x=,c=getchar(),f=;
for(;c<||c>;c=getchar())
if(!(c^))
f=-;
for(;c>&&c<;c=getchar())
x=(x<<)+(x<<)+c-;
return x*f;
}
vector<int>v;
map<int,int>h;
int Qcnt,Pcnt,a[M],ans[M],c[M],n,tmp[M];
struct Que{
int x,y,typ,cur,kth,pos;
}q[M],q1[M],q2[M];
inline void add(int x,int d){
for(;x<=n;x+=x&(-x))
c[x]+=d;
}
inline int sum(int x){
int res=;
for(;x;x-=x&(-x))
res+=c[x];
return res;
}
void divide(int hd,int tl,int l,int r){
if(hd>tl)
return;
if(l==r){
for(int i=hd;i<=tl;i++)
if(q[i].typ==)
ans[q[i].pos]=l;
return;
}
int mid=l+r>>;
for(int i=hd;i<=tl;i++){
if(q[i].typ==)
tmp[i]=sum(q[i].y)-sum(q[i].x-);
else
if(q[i].typ==&&q[i].y<=mid)
add(q[i].x,-);
else
if(q[i].typ==&&q[i].y<=mid)
add(q[i].x,);
}
for(int i=hd;i<=tl;i++){
if(q[i].typ==&&q[i].y<=mid)
add(q[i].x,);
else
if(q[i].typ==&&q[i].y<=mid)
add(q[i].x,-);
}
int l1=,l2=;
for(int i=hd;i<=tl;i++){
if(q[i].typ==){
if(q[i].cur+tmp[i]>=q[i].kth)
q1[++l1]=q[i];
else{
q[i].cur+=tmp[i];
q2[++l2]=q[i];
}
}
else{
if(q[i].y<=mid)
q1[++l1]=q[i];
else
q2[++l2]=q[i];
}
}
for(int i=;i<=l1;i++)
q[hd+i-]=q1[i];
for(int i=;i<=l2;i++)
q[hd+l1+i-]=q2[i];
divide(hd,hd+l1-,l,mid);
divide(hd+l1,tl,mid+,r);
}
int main(){
int m,T;
T=read();
while(T--){
Qcnt=Pcnt=;
v.clear();
h.clear();
memset(a,,sizeof(a));
memset(c,,sizeof(c));
memset(tmp,,sizeof(tmp));
n=read(),m=read();
for(int i=;i<=n;i++){
a[i]=read();
q[++Qcnt].x=i;
q[Qcnt].y=a[i];
q[Qcnt].typ=;
v.push_back(a[i]);
}
for(int i=;i<=m;i++){
char sign[];
int x,y,z;
scanf("%s",sign);
if(sign[]=='Q'){
x=read(),y=read(),z=read();
q[++Qcnt].x=x;
q[Qcnt].y=y;
q[Qcnt].kth=z;
q[Qcnt].typ=;
q[Qcnt].cur=;
q[Qcnt].pos=++Pcnt;
}
else{
x=read(),y=read();
q[++Qcnt].x=x;
q[Qcnt].y=a[x];
q[Qcnt].typ=;
q[++Qcnt].x=x;
q[Qcnt].y=y;
q[Qcnt].typ=;
a[x]=y;
v.push_back(y);
}
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=;i<v.size();i++)
h[v[i]]=i+;
for(int i=;i<=Qcnt;i++)
if(q[i].typ<=)
q[i].y=h[q[i].y];
divide(,Qcnt,,v.size());
for(int i=;i<=Pcnt;i++)
printf("%d\n",v[ans[i]-]);
}
return ;
}

其实,我还写了可持久化线段树的写法,只是因为空间不够溢出了,返回Segment Fault,但程序本身没有什么错误

#include <stdio.h>
#include <string.h>
#define mid (l + r >> 1) const int MAXN = 5e4 + ;
const int INF = 1e9 + ; int Rin(){
int x = , c = getchar(), f = ;
for(; c < || c > ; c = getchar())if(!(c ^ ))f = -;
for(; c > && c < ; c = getchar())x = (x << ) + (x << ) + c - ;
return x * f;
} int tot, n, m, a, b, v[MAXN], L[MAXN * ], R[MAXN * ], rt[MAXN * ], ls[MAXN * ], rs[MAXN * ], sum[MAXN * ]; void Modify(int &pr, int pl, int l, int r, int v, int d){
pr = ++tot; sum[pr] = sum[pl] + d; ls[pr] = ls[pl]; rs[pr] = rs[pl];
if(l + < r)
v < mid ? Modify(ls[pr], ls[pl], l, mid, v, d) : Modify(rs[pr], rs[pl], mid, r, v, d);
} int Query(int l, int r, int k){
if(l + == r)return l;
int suml = , sumr = ;
for(int i = ; i <= a; i++)
suml += sum[ls[L[i]]];
for(int i = ; i <= b; i++)
sumr += sum[ls[R[i]]];
if(sumr - suml >= k){
for(int i = ; i <= a; i++)
L[i] = ls[L[i]];
for(int i = ; i <= b; i++)
R[i] = ls[R[i]];
return Query(l, mid, k);
}
for(int i = ; i <= a; i++)
L[i] = rs[L[i]];
for(int i = ; i <= b; i++)
R[i] = rs[R[i]];
return Query(mid, r, k - sumr + suml);
} int main(){
int x, y, k, T = Rin(); char sign[];
while(T--){
tot = ;
memset(v, , sizeof(v));
memset(rt, , sizeof(rt));
memset(ls, , sizeof(ls));
memset(rs, , sizeof(rs));
memset(L, , sizeof(L));
memset(R, , sizeof(R));
memset(sum, , sizeof(sum));
n = Rin(), m = Rin();
for(int i = ; i <= n; i++){
v[i] = Rin();
for(int j = i; j <= n; j += j & (-j))
Modify(rt[j], rt[j], , INF, v[i], );
}
while(m--){
scanf("%s", sign);
if(sign[] == 'Q'){
a = b = ; x = Rin() - , y = Rin(), k = Rin();
for(int j = x; j; j -= j & (-j))
L[++a] = rt[j];
for(int j = y; j; j -= j & (-j))
R[++b] = rt[j];
printf("%d\n", Query(, INF, k));
}
else{
x = Rin(), y = Rin();
for(int j = x; j <= n; j += j & (-j))
Modify(rt[j], rt[j], , INF, v[x], -);
v[x] = y;
for(int j = x; j <= n; j += j & (-j))
Modify(rt[j], rt[j], , INF, v[x], );
}
}
}
return ;
}