[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;
}

謝謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章