省选专练[SCOI2014]方伯伯的OJ

时间:2022-12-16 12:47:46

大大大大大!!!!!!数据结构题。

太大了:

法一:线段树动态开点:注意n有1e8于是考虑m影响的。

法二:两个SPLAY:目的是在n有1e8时一棵维护名次,一棵维护编号。目的是解决n

原理:修改k:则剖成(1,k-1)(k,k)(k+1,n);

最多复杂度:logn*m;

法三::一个SPLAY,另外一个用map就可以了。用一个in记录是否到了前端与后缀。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<map>
using namespace std;
inline void read(int &x){//fast read
	int f=1;
	x=0;
	char ch=getchar(); 
	while(ch<'0'||ch>'9'){
		if(ch=='-'){
			f=-1;
		}
		ch=getchar();
	}
	while(ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	x*=f;
}
int n,m,d;//d is the max num that the tree could expand using by the SET
struct SET{//a set to make sure the line is priority
	#define N (int) 2e5
	int a[N*3];
	int id[N];//idx just record the left sum and that is priority
	int n;
	int insert(int idx){//维持单调的本质原因是为了保证在查询编号时可以得到在哪一块 
		n++;
		id[n]=idx;
		int i=n+d;//we have two sum should be recorded;
		a[i]=1;
		while(i>>=1){
			a[i]++;
		}
		return n;
	}
	void del(int i){// del a point and expand 1 to 3 points 
		i+=d;
		a[i]=0;
		while(i>>=1){
			a[i]--;
		}
	} 
	int rank(int i){//give you a sum and que the rank;
		i+=d;
		int ans=a[i];
		while(i){
			if(i%2==1){
				ans+=a[i-1];
			}
			i/=2;
		}
		return ans;
	}
	int get_by_rank(int x){//give you the rank ans que the sum;
		int i=1;
		while(i<=d){
			if(x<=a[i*2]){
				i*=2;
			}
			else{
				x-=a[i*2];
				i=i*2+1;
			}
		}
		return id[i-d];
	}
}pre,suf;//pre尚未开封的左端(名次小)suf尚未开封的右端(名次大) 
struct SPlAY{
	#define cl(x) c[x][0]
	#define cr(x) c[x][1]
	int fa[N];
	int c[N][2];
	int siz[N];
	int v[N];
	int rt;
	int n;
	int rank(int x){
		int ans=x;
		int i=rt;
		while(i){
			if(v[i]>x){
				i=cl(i);
			}
			else{
				ans-=siz[cl(i)]+1;
				i=cr(i); 
			}
		}
		return ans;
	}
	void sc(int y,int x,bool flag){
		fa[x]=y;
		c[y][flag]=x;
	}
	bool get(int x){
		return x==c[fa[x]][1];
	} 
	void pushup(int x){
		siz[x]=siz[cl(x)]+siz[cr(x)]+1;
	}
	void rotate(int x){
		int y=fa[x];
		bool flag=get(x);
		if(y==rt){
			fa[rt=x]=0;
		}
		else{
			sc(fa[y],x,get(y));
		}
		sc(y,c[x][!flag],flag);
		sc(x,y,!flag);
		pushup(y);
	}
	void splay(int x,int to=0/*depends on the root*/){
		int y;
		while(y=fa[x],y!=to){
			if(fa[y]==to){
				rotate(x);
				break;
			}
			else{
				rotate(get(x)==get(y)?y:x);
				rotate(x);
			}
		}
		pushup(x);
	}
	void insert(int x){//insert the sum to the SPLAY tree   
		n++;
		v[n]=x;
		siz[n]=1;
		if(!rt){
			rt=n;
			return;
		}
		int i=rt;
		while(1){
			bool d=v[i]<x;
			if(c[i][d])
				i=c[i][d];
			else{
				sc(i,n,d);
				splay(n);
				return;
			}
		}
	}
	map<int,int> dy;
	int DY(int x){//hash the changing name of the num
		if(!dy.count(x)){
			return x;
		}
		return dy[x];
	}
	int get_by_rank(int x){//got the sum get the rank值找编号 
		if(!rt){
			return DY(x);
		}
		int i=rt;
		int i0;
		int rank=1;
		while(i){
			i0=i;
			if(x>v[i]-(siz[cl(i)]+rank)){
				rank+=siz[cl(i)]+1;
				i=c[i][1];
			}
			else{
				i=c[i][0];
			}
		}
		splay(i0);
		return DY(x+rank-1);
	}
}out;
const int PRE=1;//PRE means at the head of the queue
const int SUF=2;//SUF means at the tail of the queue
struct point{
	int in,id;//in means if that has been in the queue already and the location that is
};
map<int,point> belong;//the location of the i;
point &B(int x){//这个函数的目的实在映射中寻找这个编号的位置.打传递是因为要通过这个改变belong所hash的值 
	if(!belong.count(x))
		belong[x]=(point){0,x};//如果没有hash过那么必然这时还没有过挪动到队首或队尾 
	return belong[x];
}
int get_rank(int x){//已知编号求名次 
	if(B(x).in==1){
		return pre.a[1]+1-pre.rank(B(x).id);//a 数组在这里表示最左端名次(最大),然后加一是因为计算自己。 
	}
	if(B(x).in==2){
		return n-suf.a[1]+suf.rank(B(x).id);
	}
	return pre.a[1]+out.rank(B(x).id);//如果已经加入splay内,则先加上右端所有的值然后再加上splay中的rank 
}
//引理:pre中的最大名次<out中的最小名次<=out中的最大名次<suf中的最小名次。 
int get_by_rank(int x){//已知名次求编号。 
	if(x<=pre.a[1]){//引理1 
		return pre.get_by_rank(pre.a[1]-x+1);//在pre中的名次是总名次-实际名次+1,因为pre是倒过来的 
	}
	if(x>n-suf.a[1]){//引理3 
		return suf.get_by_rank(x-(n-suf.a[1]));//减去suf之前的所有名次,则是在suf中的实际名次。 
	}//引理2 
	return out.get_by_rank(x-pre.a[1]);//减去pre中的所有名次则是在out中的名次。 
} 
int main(){
	read(n);
	read(m);
//	cout<<n<<" "<<m<<endl; 
	for(d=1;d<m;d*=2);// get the max sum may be
	d--;
	int lastans=0;//must online
	for(int i=1;i<=m;i++){
//		cout<<"number "<<i<<endl;
		int flag;
		read(flag);
		int ans;
		if(flag==4){
			int k;
			read(k);
			k-=lastans;
			ans=get_by_rank(k);//已知名次反向求编号。 
		}
		else{
			int k;
			read(k);
			k-=lastans;
			ans=get_rank(k);//这三中操作都是要求用编号求名次。 
			if(flag==1){
				int to;
				read(to);
				to-=lastans;
				B(to)=B(k);
				if(B(k).in){//出现 in 则必然是改变块,否则是原块 
					(B(k).in==PRE?pre:suf).id[B(k).id]=to;//change the sum to the loc should be and hash the change sum
				}
				else{
					out.dy[B(k).id]=to;//if not hash the change sum; 
				}
			}
			else{//必然会挪动到队首或队尾 
				if(B(k).in==0){//如果没有挪动过,那么在SPLAY插入这个下标。 
					out.insert(B(k).id);
				}
				else
					(B(k).in==PRE?pre:suf).del(B(k).id);//因为会被挪动过,所以删去原本的点。
				if(flag==2){
					B(k)=(point){PRE,pre.insert(k)};//go ahead 
				}
				else{
					B(k)=(point){SUF,suf.insert(k)};//turn back 
				}
			}
		}
		printf("%d\n",ans);
		lastans=ans;//must online
	}
	return 0;//or wrong answer
}

注解很丰富。