带插入区间K小值 - 更为优秀的解法

新写一篇题解,彻底了结这道毒瘤卡常题

从我的上一篇带插入区间K小值题解最后得到了一个时间复杂度为 \(O(n \times \sqrt {n \log {n}} \times \log {n})\)的解法,凭借优越的常数得到了60分的成绩,现在又有了一种更为优秀的做法。
用块状链表可以实现单次插入 \(O(\sqrt {n})\)的复杂度,假设没有了插入操作,就变成了这样一个题目:

  • 修改某一位置的值;
  • 询问区间第\(k\)小。

这其实是lxl的Ynoi 2018 未来日记的弱化版。由于没有插入操作,所以可以将数列分成\(\sqrt {n}\) 块,同时将权值分块,令\(val[i][j]\) 表示前\(i\)块权值在第\(j\)个值域的数的个数,\(w[i][j]\)表示前\(i\)块权值为\(j\)的数的个数。修改暴力跑一遍前缀和即可,询问先早答案暴力从小到大枚举在哪一个值域里,然后在值域里暴力枚举答案即可。将分块写成块状链表,即可 \(O(n \sqrt {n})\)的复杂度,适当卡常即可通过此题。

代码:

#include<bits/stdc++.h>
#pragma GCC optimize(3)//找一个人少的时候用这个卡过去
#define re register
using namespace std;
const int N=70000,M=275;
struct BLO {
	int lf,rf;
	int sz,a[M*2+50],val[M+10],w[N+10];
	//a[]:原数列  val[]:权值分块  w[]:权值 
};BLO t[M+10]; int hed=0,tot=0;

int n,m,ans=0,Val[M+10],W[N+10];
char pr[1000010]; int top=0;

inline int read() {
	re int x=0;re char c=getchar();
	while(c<'0'||c>'9')	c=getchar();
	while(c>='0'&&c<='9')	x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x;
}
inline int Min(int x,int y) { return x<y?	x:y; }
void print(int x) { if(x>9)	print(x/10); pr[top++]=x%10+'0'; }//输出优化
void init() {
	n=read(),hed=0,tot=n/M+1;
	for(re int i=1,x;i<=n;++i) {
		x=read(),t[i/M+1].a[++t[i/M+1].sz]=x;
		++t[i/M+1].w[x],++t[i/M+1].val[x/M+1];
	}
	for(int i=0;i<tot;++i)	t[i].rf=i+1;
	for(int i=1;i<=tot;++i)	t[i].lf=i-1;
	for(int i=1;i<=tot;++i) {
		for(int j=0;j<=N;j++) t[i].w[j]+=t[i-1].w[j];
		for(int j=1;j<=M;j++)	t[i].val[j]+=t[i-1].val[j];
	}
	m=read();
}
void split(int u) {
	++tot;
	t[tot].lf=t[u].lf,t[t[u].lf].rf=tot,t[tot].rf=u,t[u].lf=tot;
	for(re int i=0;i<=N;i++)	t[tot].w[i]=t[u].w[i];
	for(re int i=1;i<=M;i++)	t[tot].val[i]=t[u].val[i];
	for(re int i=1;i<=M;i++)	t[tot].a[i]=t[u].a[i];
	for(re int i=M+1;i<=t[u].sz;++i)
		t[u].a[i-M]=t[u].a[i],
		--t[tot].val[t[u].a[i]/M+1],--t[tot].w[t[u].a[i]];
	t[tot].sz=M,t[u].sz-=M;
}
void insert(int x,int k) {
	int u=t[hed].rf; for(;x-1>t[u].sz;x-=t[u].sz,u=t[u].rf);
	++t[u].sz;
	for(re int i=t[u].sz;i>x;--i) t[u].a[i]=t[u].a[i-1];
	t[u].a[x]=k; int tmp=u;
	while(u)	++t[u].val[k/M+1],++t[u].w[k],u=t[u].rf;
	if(t[tmp].sz==M*2)	split(tmp);
}
void modify(int x,int New) {
	int u=t[hed].rf; for(;x>t[u].sz;x-=t[u].sz,u=t[u].rf);
	int Old=t[u].a[x]; t[u].a[x]=New;
	while(u)
		--t[u].val[Old/M+1],--t[u].w[Old],
		++t[u].val[New/M+1],++t[u].w[New],
		u=t[u].rf;
}
void query(int x,int y,int k) {
	int u=t[hed].rf,v=t[hed].rf; ans=0; bool flag=0;
	for(;x>t[u].sz;x-=t[u].sz,u=t[u].rf);
	for(;y>t[v].sz;y-=t[v].sz,v=t[v].rf);
	for(re int i=1;i<x;++i)//边角块
		--Val[t[u].a[i]/M+1],--W[t[u].a[i]];
	for(re int i=1;i<=y;++i)
		++Val[t[v].a[i]/M+1],++W[t[v].a[i]];
	for(int i=1;;++i) {
		if(Val[i]+t[t[v].lf].val[i]-t[t[u].lf].val[i]>=k) {
			int mx=Min(i*M,N+1);
			for(int j=(i-1)*M;j<mx;++j) {
				if(W[j]+t[t[v].lf].w[j]-t[t[u].lf].w[j]>=k)
					{ ans=j,flag=1;break; }
				else
					k-=(W[j]+t[t[v].lf].w[j]-t[t[u].lf].w[j]);
			}
			break;
		}
		k-=(Val[i]+t[t[v].lf].val[i]-t[t[u].lf].val[i]);
	}
	for(re int i=1;i<x;++i)//恢复(比memset快)
		++Val[t[u].a[i]/M+1],++W[t[u].a[i]];
	for(re int i=1;i<=y;++i)
		--Val[t[v].a[i]/M+1],--W[t[v].a[i]];
}
void debug() {
	for(int i=t[hed].rf;i;i=t[i].rf) {
		printf(" ");
		for(int j=1;j<=t[i].sz;j++)	printf("%d ",t[i].a[j]);
	}
	printf("\n");
}
int main() {
	#ifndef ONLINE_JUDGE
	freopen("Dynamic Kth in range.in","r",stdin);
	freopen("Dynamic Kth in range.out","w",stdout);
	int cur=clock();
	#endif
	init(); int x,y,k; char opt;
	for(;m;--m) {
		opt=getchar();
		while(opt<'A'||opt>'Z')	opt=getchar();
		x=read()^ans,y=read()^ans;
		switch(opt) {
			case 'Q':
				k=read()^ans,query(x,y,k);
				print(ans),pr[top++]=10;
				break;
			case 'I':	insert(x,y);break;
			default:	modify(x,y);break;
		}
		#ifndef ONLINE_JUDGE
		ans=0;
		#endif
	}
	pr[--top]='\0',puts(pr);
	#ifndef ONLINE_JUDGE
	printf(">>> %d ms.\n",clock()-cur);
	fclose(stdin),fclose(stdout);
	#endif
	return 0;
}
posted @ 2018-12-27 08:01  daniel14311531  阅读(363)  评论(0编辑  收藏  举报