题意:给定一个序列,两种操作:1、求修改第i个数为Vi;2、求区间[L,R]第K大的值。
2015多校练习赛的一道题。因为是经典题所以很多人都直接把模板敲上去了,然而通过率却惨不忍睹。我也是直接套以前写的程序的SB之一。然而分块时间复杂度太高并不能通过本题(分块做法见:http://blog.csdn.net/nkwbtb/article/details/21639647)于是愉快的TLE了。考场上顾不上啥,分块不行就换了线段树套平衡树上,结果MLE了。
线段树套平衡树的做法是这样的:
线段树的每一个节点对应一颗平衡树,平衡树中存的是对应区间的数。
修改操作可对应线段树的单点修改,将对应节点平衡树中的当前数删除,再插入修改后的数。
询问操作则是,先二分答案,然后在对应线段树节点的每一刻平衡树中询问比答案小的数有多少个,将这些数加起来。最终二分到K的时候就是最终答案。
空间复杂度(NlogN)
一次修改的时间复杂度O(log^2N)
一次询问的时间复杂度O(log MAXN*log^2N) (MAXN是二分答案的上界)
然而这样做的话,由于线段树的常数问题会爆空间。
赛后,突然想到区间小于Num有多少个这个维护量是满足区间减法的。那么我们可以把线段树换成树状数组,不仅常数下降,所需要的空间也会减少。但是这样做依然会TLE。
附树状数组套平衡树(TLE):
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <deque>
#define MAXN 100002
#define INF 0x7fffffff
using namespace std;
using namespace __gnu_pbds;
struct value{int val;int t;};
struct cmp{
bool operator ()(value a,value b)const{
if(a.val==b.val)return a.t<b.t;
return a.val<b.val;}
};
tree<value,null_type,cmp,rb_tree_tag,tree_order_statistics_node_update>bit[MAXN];
tree<value,null_type,cmp,rb_tree_tag,tree_order_statistics_node_update>::iterator it;
//exSTL中的可以求Krank的平衡树
int num[MAXN],n,ql,qr,k,minn=INF,maxn=-INF,oper;
//minn是二分的下界,maxnn是二分的上界
int lowbit(int x){return x&(-x);}
void fread(int &p){
char ch;
do{ch=getchar();}while(ch<'0'||ch>'9');
p=ch-'0';
while(1){ch=getchar();if(ch<='9'&&ch>='0')p=p*10+ch-'0';else break;}
}
//快速读
int getorder(){
int suml=0,sumr=0;
for(int i=ql-1;i>0;i-=lowbit(i))
suml+=bit[i].order_of_key((value){k,0});
for(int i=qr;i>0;i-=lowbit(i))
sumr+=bit[i].order_of_key((value){k,0});
//printf("%d %d\n",suml,sumr);
return sumr-suml;
}
//或得对应区间小于二分出的答案的数字有多少个
int b_s(int l,int r,int q){
while(l!=r){
k=(l+r)>>1;
//printf("%d ",k);
if(getorder()>=q)r=k;
else l=k+1;
}
return l-1;
}
//二分答案
void update(int p,int num){
for(int i=p;i<=n;i+=lowbit(i))
bit[i].insert((value){num,p});
}
//树状数组更新
void delet(int p,int num){
for(int i=p;i<=n;i+=lowbit(i))
bit[i].erase((value){num,p});
}
//树状数组删除
int main(){
int t;
while(scanf("%d",&n)!=EOF){
minn=INF,maxn=-INF;
for(int i=1;i<=n;i++){
bit[i].clear();
fread(num[i]);
minn=min(num[i]-1,minn);
maxn=max(num[i]+1,maxn);
}
for(int i=1;i<=n;i++)update(i,num[i]);
fread(t);
while(t--){
int opt;
fread(opt);
if(opt==2){
fread(ql);fread(qr);fread(k);
printf("%d\n",b_s(minn,maxn,k));
}else if(opt==1){
fread(ql);fread(k);
delet(ql,num[ql]);
update(qr,k);
num[ql]=k;
minn=min(num[ql]-1,minn);
maxn=max(num[ql]+1,maxn);
}
}
}
return 0;
}
限制我们的时间复杂度的瓶颈在于二分答案,那么我们能否不二分答案呢。答案是肯定的,题解中给出的就是这样的做法。
我们还是树状数组套平衡树,但是维护的东西略有不同,我们树状数组的每一个节点代表对应的数字(需要将询问读入,然后把所有出现的数字离散化),平衡树中保存每一个数在序列中的下标。
修改:将原来序列中的数字在对应的树状数组套的平衡树中删除,再同理插入新的数字
询问:考虑答案的二进制表示,通过巧妙地运用树状数组的性质,我们可以从高位往地位贪心的构造答案,每次贪心在平衡树中查找对应区间的数字个数。
修改时间复杂度O(log^2N)
询问时间复杂度O(log^2N)构造答案+树状数组单点查询+平衡树查询
14605639 | 2015-08-22 19:06:44 | Accepted | 5412 | 2355MS | 63596K | 2041 B | G++ | Kurama |
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#define MAXN 100002
using namespace std;
using namespace __gnu_pbds;
struct query{int t,l,r,k;}q[MAXN];
//保存询问
tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>bit[MAXN*2];
tree<int,null_type,less<int>,rb_tree_tag,tree_order_statistics_node_update>::iterator it;
//exSTL红黑树,G++ Only,低版本中null_type为null_mapped_type
int num[MAXN],n,ql,qr,k,oper,lsh[MAXN*2],H,ct;
//lsh为离散化数组 ,H为贪心构造答案的最高位
int lowbit(int x){return x&(-x);}
void add(int p,int num){
for(int i=p;i<=ct;i+=lowbit(i))
bit[i].insert(num);
}
//树状数组更新
void del(int p,int num){
for(int i=p;i<=ct;i+=lowbit(i))
bit[i].erase(num);
}
//树状数组删除
int pos(int num){return lower_bound(lsh,lsh+ct,num)-lsh+1;}
// 返回离散化下标
int query(int l,int r,int k){
int num=0;
for(int i=H;i;i>>=1){ //高位往地位贪心
int tmp=num+i;
if(tmp>ct)continue;
int kth=bit[tmp].order_of_key(r+1)-bit[tmp].order_of_key(l);
//平衡树查询
if(kth>=k)continue;
num=tmp,k-=kth;
}
return num;
}
//贪心构造答案
int main(){
int t;
while(scanf("%d",&n)!=EOF){
ct=0;
for(int i=1;i<=n;i++){
scanf("%d",&num[i]);
lsh[ct++]=num[i];
}
scanf("%d",&t);
for(int i=0;i<t;i++){
scanf("%d",&q[i].t);
if(q[i].t==1){
scanf("%d%d",&q[i].l,&q[i].k);
lsh[ct++]=q[i].k;
}else scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].k);
}
sort(lsh,lsh+ct);
ct=unique(lsh,lsh+ct)-lsh;
for(int i=1;i<=ct;i++)bit[i].clear();
H=1;
while(H*2<ct)H*=2;
for(int i=0;i<t;i++)if(q[i].t==1)q[i].k=pos(q[i].k);
for(int i=1;i<=n;i++){
num[i]=pos(num[i]);
add(num[i],i);
}
for(int i=0;i<t;i++){
if(q[i].t==1){
del(num[q[i].l],q[i].l);
add(q[i].k,q[i].l);
num[q[i].l]=q[i].k;
}else printf("%d\n",lsh[query(q[i].l,q[i].r,q[i].k)]);
}
}
return 0;
}