题解 P4278 【带插入区间K小值】
题意:题如其名,带插入区间K小值。强制在线。
原题
这道题按道理来说是可以用奇妙的树套树两个
那么能过的方法,就只有块状链表+值域分块了。
首先来介绍一下值域分块:
先看看怎么维护一个数列(不是区间)的静态K小值:
将值域(默认与
弄两个数组,一个
然后对于整个K小值,我们可以遍历每一块值域,比较K与当前块内数的个数(
再来看看区间静态K小值:
仍然是值域分块,不过这次利用前缀和的思想。把序列也分成
在弄两个一维数组
对于询问
若
否则,先暴力处理散块,更新
#include <cstdio>
const int maxn=1e5+5,maxl=300;int maxx;
struct IO{
IO(){};char c;
inline char gc(){
static char buf[maxn],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,maxn,stdin),p1==p2)?EOF:*p1++;
}
inline IO&operator>>(int &_){
_=0;bool f=1;c=gc();while(c<'0'||c>'9'){if(c=='-') f=0; c=gc();}
while(c>='0'&&c<='9'){_=_*10+c-48;c=gc();}if(!f) _=-_;return *this;
}
inline IO&operator<<(int x){
if(!x){putchar(48);putchar('\n');return *this;}
static int wt[40],len;len=0;if(x<0){putchar('-');x=-x;}
for(;x;x/=10){wt[++len]=x%10;}
while(len){putchar(wt[len--]+48);}
putchar('\n');return *this;
}
}io;
int n,m,block_size,last_ans;
int Size[maxl*3][maxl+5],Alive[maxl*3][maxn+5],pre_len[maxn],bl[maxn];//bl用来管下标的所属块,pre_len[i]表示c[i]前有几个数
int s[maxl+5],cnt[maxn];
inline int max(int a,int b){return a>b?a:b;}
inline int min(int a,int b){return a<b?a:b;}
inline int L(int id){return (id-1)*maxl+1;}
inline int R(int id){return min(id*maxl,maxx);}//这个用来管值域的分块情况
inline int where(int val){return (val-1)/maxl+1;}//值域所属块
struct Block{
int size[maxl+5],alive[maxn];
int a[(maxl<<1)+5],len;int lb,rb;
inline void ins(int val){size[where(val)]++,alive[val]++;}
inline void del(int val){size[where(val)]--,alive[val]--;}
inline void insert(int pos,int val){
len++;for(int i=len;i>=pos+2;i--) a[i]=a[i-1];
a[pos+1]=val;//将val插到pos后面
}
inline void push_back(int val){a[++len]=val;}
int& operator[](int id){
return a[id];//这样会很舒爽
}
}c[maxl];int block_len=0;
inline void modify(int pos,int val){
int id=bl[pos],_=pos-pre_len[id];int old=c[id][_];
c[id].del(old),c[id].ins(val);c[id][_]=val;
for(int i=id;i;i=c[i].rb){
Size[i][where(old)]--;Alive[i][val]++;
Alive[i][old]--;Size[i][where(val)]++;
}
}
inline void ins(int val){s[where(val)]++,cnt[val]++;}
inline void del(int val){s[where(val)]--,cnt[val]--;}
inline int query(int key){
for(int i=1;i<=where(maxx);i++){
if(key<=s[i]){
for(int j=L(i);j<=R(i);j++){
if(key<=cnt[j]) return j;
key-=cnt[j];
}
}
else key-=s[i];
}//值域分块的玄妙操作
return 0;
}
inline int Query(int l,int r,int key){
int idl=bl[l],idr=bl[r];
if(idl==idr){
for(int i=l;i<=r;i++) ins(c[idl][i-pre_len[idl]]);
int res=query(key);
for(int i=l;i<=r;i++) del(c[idl][i-pre_len[idl]]);
return res;
}
for(int i=l;i<=pre_len[c[idl].rb];i++) ins(c[idl][i-pre_len[idl]]);
for(int i=pre_len[idr]+1;i<=r;i++) ins(c[idr][i-pre_len[idr]]);
int res=0;
for(int i=1;i<=where(maxx);i++){
int now=s[i]+Size[c[idr].lb][i]-Size[idl][i];
if(key<=now){
for(int j=L(i);;j++){
int _=cnt[j]+Alive[c[idr].lb][j]-Alive[idl][j];
if(key<=_){res=j;break;}
else key-=_;
}break;
}else key-=now;
}//值域分块,多么美妙
for(int i=l;i<=pre_len[c[idl].rb];i++) del(c[idl][i-pre_len[idl]]);
for(int i=pre_len[idr]+1;i<=r;i++) del(c[idr][i-pre_len[idr]]);
return res;
}
void split(int id){//把第id块拆了
c[++block_len].lb=id;c[block_len].rb=c[id].rb;c[c[id].rb].lb=block_len;c[id].rb=block_len;
int now=block_size+1;for(;now<=c[id].len;now++){c[block_len].push_back(c[id][now]),c[block_len].ins(c[id][now]),c[id].del(c[id][now]);}
pre_len[block_len]=pre_len[id]+(c[id].len=block_size);
for(int i=pre_len[block_len]+1;i<=pre_len[block_len]+c[block_len].len;i++) bl[i]=block_len;
for(int i=1;i<=where(maxx);i++) Size[block_len][i]=(Size[id][i]=Size[c[id].lb][i]+c[id].size[i])+c[block_len].size[i];
for(int i=1;i<=maxx;i++) Alive[block_len][i]=(Alive[id][i]=Alive[c[id].lb][i]+c[id].alive[i])+c[block_len].alive[i];
}
void insert(int pos,int val){
int id=bl[pos];int _=pos-pre_len[id];
c[id].insert(_,val);for(int i=c[id].rb;i;i=c[i].rb) pre_len[i]++;c[id].ins(val);
for(int i=id;i;i=c[i].rb){
Size[i][where(val)]++,Alive[i][val]++;
bl[pre_len[i]+c[i].len]=i;bl[pre_len[i]+1]=i;
}
if(c[id].len>=block_size*2) split(id);//要是发现它不对劲
}
void build(){
block_size=maxl;for(int i=1;i<=n;i++) bl[i]=(i-1)/block_size+1;
block_len=bl[n];bl[0]=1;//我的代码中要是被要求把一个数插到第一个数,他会去找第零个数在哪块里面
for(int i=1;i<=block_len;i++){
c[i].lb=i-1,c[i].rb=i+1;
}
c[block_len].rb=0;
for(int i=1;i<=n;i++){
int x;io>>x;maxx=max(maxx,++x);//因为我讨厌0,所以所有出现的价值都被我加1了
c[bl[i]].push_back(x);c[bl[i]].ins(x);
}
for(int i=1;i<=block_len;i++){
pre_len[i]=pre_len[i-1]+c[i-1].len;
for(int j=1;j<=where(maxx);j++) Size[i][j]=Size[i-1][j]+c[i].size[j];
for(int j=1;j<=maxx;j++) Alive[i][j]=Alive[i-1][j]+c[i].alive[j];
}
}
int main(){
io>>n;build();io>>m;
for(int i=1;i<=m;i++){
char opt=io.gc();while(opt>'Z'||opt<'A') opt=io.gc();
if(opt=='M'){
int pos,val;io>>pos>>val;pos^=last_ans,val^=last_ans;maxx=max(maxx,val+1);
modify(pos,val+1);
}
if(opt=='Q'){
int l,r,k;io>>l>>r>>k;l^=last_ans,r^=last_ans,k^=last_ans;
io<<(last_ans=Query(l,r,k)-1);//由于我把所有出现元素+1,这里要-1
}
if(opt=='I'){
int pos,val;io>>pos>>val;pos^=last_ans,val^=last_ans;maxx=max(maxx,val+1);
insert(pos-1,val+1);//意思是把(val+1)塞到(pos-1)后面
}
}
return 0;
}
当然,需要吸氧才能卡过时限。这里我选择维护每个点的所属块和每个块之前有几个数,你也可以选择不维护,要用的时候
你甚至可以在块内部维护一个链表。这个写法常数比起块内维护数组要大一些,不过亲测也能卡过去。