[BZOJ3065]带插入区间K小值

带插入区间K小值

题解

很明显的块状链表做法。分块万岁

我们先对值域进行分块,只要知道每个块中每个数的出现次数,很容易就可以在O\left(\sqrt{n} \right )的时间内得到第k小。

然后我们需要维护一个序列,进行序列分块。

询问时,我们需求出一段区间内每个数,每个块的出现次数。这可以通过前缀和快速的求出,之前统计下就可以了。对于每个值域块与数字的出现次数。查询时两端暴力处理,中间前缀和直接差分。这样预处理O\left(n\sqrt{n} \right ),查询O\left(\sqrt{n} \right )

修改时也只需要暴力更新块中前缀的信息,O\left(\sqrt{n} \right )就可以解决。

由于有插入信息的存在,需要用块状链表来动态维护,也是O\left(\sqrt{n} \right )可以解决的。

我们每次在一个块中插入一个数时,这个块都会多出一个数,我们可以将这个多的数插到下一个块中。而插入的这个数又只会影响这个块中的信息的开头,我们之后会暴力维护,我们更新时也只需更新插入块中的前缀。这更新也只用O\left(\sqrt{n} \right )的时间。

所以,整个过程的时间复杂度是O\left((n+q)\sqrt{n} \right )的,可以过去。

源码

#include<cstdio>
#include<iostream>
#include<list>
using namespace std;
#define MAXN 300
#define MAXM 70500
typedef long long LL; 
typedef list<int>::iterator iter;
char buf[(int)3e7],*ss=buf;
inline int init(){buf[fread(buf,1,(int)3e7-1,stdin)]='\n';fclose(stdin);return 0;}
const int __START__=init();
inline int read(){
    int d=0;
    while(!isdigit(*ss))++ss;
    while(isdigit(*ss))d=d*10+(*ss++^'0');
    return d;
}
#define bel(x) ((x-1)/siz+1)
const int blocks=235;
const int siz=300;
int n,m,ans,q,L[MAXN],R[MAXN];
int pre[MAXM][MAXN],prb[MAXN][MAXN];
int tot[MAXM],tob[MAXN];
int stak,sta[1005];
list<int> s[MAXN];
int modify(list<int>&l,int x,int v){
	iter it=l.begin();
	while(x--)++it;
	const int ret=*it;*it=v;
	return ret;
}
void get(list<int>&l,int x,int s){
	iter it=l.begin();
	while(x--)++it;
	while(s--)++tot[sta[++stak]=*it],++tob[bel(*it)],++it;
}
signed main(){
	n=read();
	for(int i=1;i<=blocks;i++)L[i]=R[i-1]+1,R[i]=i*siz;
	for(int i=1;i<=n;i++){
		int x=read()+1;
		const int id=bel(i);s[id].push_back(x);
		int *P=pre[x],*B=prb[bel(x)];
		for(int j=id;j<=blocks;j++)++P[j],++B[j];
	}
	for(m=read()^ans;m--;){
		while(isspace(*ss))++ss;
		switch(*ss){
			case'Q':{
				int l=read()^ans,r=read()^ans,k=read()^ans;
				stak=0;const int bl=bel(l),br=bel(r);
				if(bl==br){
					get(s[bl],l-L[bl],r-l+1);
					for(int i=1;;i++)
						if(k>tob[i])k-=tob[i];
						else{
							for(int j=L[i];;j++)
								if(k>tot[j])k-=tot[j];
								else{ans=j;break;}
							break;
						}
				}
				else{
					get(s[bl],l-L[bl],R[bl]-l+1);get(s[br],0,r-L[br]+1);
					for(int i=1;;i++)
						if(k>tob[i]+prb[i][br-1]-prb[i][bl])
							k-=tob[i]+prb[i][br-1]-prb[i][bl];
						else{
							for(int j=L[i];;j++)
								if(k>tot[j]+pre[j][br-1]-pre[j][bl])k-=tot[j]+pre[j][br-1]-pre[j][bl];
								else{ans=j;break;}
							break;	
						}	
				}
				printf("%d\n",--ans);
				for(int i=0;i<=stak;i++)tot[sta[i]]=tob[bel(sta[i])]=0;
				break;
			}
			case'M':{
				int x=read()^ans;
				int v=(read()^ans)+1;
				const int id=bel(x);
				int old=modify(s[id],x-L[id],v);
				int *Po=pre[old],*Pn=pre[v],*Bo=prb[bel(old)],*Bn=prb[bel(v)];
				for(int i=id;i<=blocks;i++)--Po[i],--Bo[i],++Pn[i],++Bn[i];
				break;
			}
			case'I':{
				int x=read()^ans;
				int v=(read()^ans)+1;
				const int id=bel(x);
				iter it=s[id].begin();
				for(int i=x-L[id];i--;)++it;
				s[id].insert(it,v);
				int *P=pre[v],*B=prb[bel(v)];
				for(int i=id;i<=blocks;i++)++P[i],++B[i];
				for(int i=id+1;i<=blocks;i++)
					if(s[i-1].size()<=siz)break;
					else{
						int vl=s[i-1].back();
						--pre[vl][i-1];--prb[bel(vl)][i-1];
						s[i-1].pop_back();
						s[i].push_front(vl);
					}
				break;
			}
		}
	}
    return 0;
}

谢谢!!!

发布了117 篇原创文章 · 获赞 154 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Tan_tan_tann/article/details/104090143