BZOJ 1500 [NOI 2005] 维修数列 (splay 模板)

本文详细解析了BZOJ1500题目的Splay树实现方法,介绍了Splay树的基本操作如旋转、下推、上拉等,并通过具体实例展示了插入、删除、修改等复杂操作的具体实现过程。

题目链接:BZOJ 1500

听说能将这道题写过splay就没有问题了吧= =。去看hzwer的博客他说遇到这种恶心的题,找一个好心的学长要一个板吧。所以我就用了他的板了。查错查了好久最后才发现是update中一个l写成了r。Orzzzzzzzzzzzz。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
#define inf (1e9)
#define maxn (1000000+3)
int N,M,root,size;
int fa[maxn],c[maxn][2],id[maxn];
int a[maxn],v[maxn],lmx[maxn],rmx[maxn],sum[maxn],mx[maxn],siz[maxn],rec[maxn];
bool tag[maxn],rev[maxn];

void pushup(int k){
	int l=c[k][0],r=c[k][1];
	siz[k]=siz[l]+siz[r]+1;
	sum[k]=sum[l]+sum[r]+v[k];
	mx[k]=max(mx[l],mx[r]);
	mx[k]=max(mx[k],rmx[l]+lmx[r]+v[k]);
	lmx[k]=max(lmx[l],sum[l]+v[k]+lmx[r]);
	rmx[k]=max(rmx[r],sum[r]+v[k]+rmx[l]);
}

void pushdown(int k){
	int l=c[k][0],r=c[k][1];
	if(tag[k]){
		tag[k]=rev[k]=0;
		if(l){tag[l]=1; v[l]=v[k]; sum[l]=v[k]*siz[l];}
		if(r){tag[r]=1; v[r]=v[k]; sum[r]=v[k]*siz[r];}
		if(v[k]>=0){
			if(l)lmx[l]=rmx[l]=mx[l]=sum[l];
			if(r)lmx[r]=rmx[r]=mx[r]=sum[r];
		}
		else{
			if(l)lmx[l]=rmx[l]=0,mx[l]=v[k];
			if(r)lmx[r]=rmx[r]=0,mx[r]=v[k];
		}
	}
	if(rev[k]){
		rev[k]^=1; rev[l]^=1; rev[r]^=1;
		swap(lmx[l],rmx[l]); swap(lmx[r],rmx[r]);
		swap(c[l][0],c[l][1]);swap(c[r][0],c[r][1]);
		pushup(k);
	}
}

void rotate(int x,int &k){
	int y=fa[x],z=fa[y],l,r;
	if(c[y][0]==x)l=0; else l=1; r=l^1;
	if(y==k)k=x;
	else{
		if(c[z][0]==y)c[z][0]=x;
		else c[z][1]=x;
	}
	fa[c[x][r]]=y; fa[y]=x; fa[x]=z;
	c[y][l]=c[x][r]; c[x][r]=y;
	pushup(y); pushup(x);
}

void splay(int x,int &k){
	while(x!=k){
		int y=fa[x],z=fa[y];
		if(y!=k){
			if((c[y][0]==x)==(c[z][0]==y))rotate(y,k);
			else rotate(x,k);
		}
		rotate(x,k);
	}
}

int find(int k,int rk){
	if(tag[k]||rev[k])pushdown(k);
	int l=c[k][0],r=c[k][1];
	if(siz[l]+1==rk)return k;
	else if(siz[l]>=rk)return find(l,rk);
	else return find(r,rk-siz[l]-1);//注意还要减去根节点
}

void built(int l,int r,int f){
	if(l>r)return ;
	int now=id[l],last=id[f];
	if(l==r){
		v[now]=mx[now]=sum[now]=a[l];
		fa[now]=last; siz[now]=1;
		if(a[l]>=0)lmx[now]=rmx[now]=a[l];//更新最大和
		else lmx[now]=rmx[now]=0;
		if(l<f)c[last][0]=now;
		else c[last][1]=now;
		return ;
	}
	int mid=(l+r)>>1; now=id[mid];
	built(l,mid-1,mid); built(mid+1,r,mid);
	v[now]=a[mid]; fa[now]=last; pushup(now);
	if(mid<f)c[last][0]=now;
	else c[last][1]=now;
}

void ins(int k,int tot){
	int x,y,z;
	for(int i=1;i<=tot;i++){
		scanf("%d",&a[i]);
		if(rec[0])id[i]=rec[rec[0]--];
		else id[i]=++size;
	}
	built(1,tot,0);
	z=id[(tot+1)>>1];//加入的这一段的根节点
	x=find(root,k+1); y=find(root,k+2);//因为我们加入了1这个虚拟节点,所以实际右移一位 
	splay(x,root); splay(y,c[x][1]);
	c[y][0]=z; fa[z]=y;
	pushup(y); pushup(x);
}

void reclaim(int k){
	if(!k)return ;
	int l=c[k][0],r=c[k][1];
	reclaim(l); reclaim(r);
	fa[k]=c[k][0]=c[k][1]=0;
	tag[k]=rev[k]=0;
	rec[++rec[0]]=k;
}

void del(int k,int tot){
	int x,y,z;
	x=find(root,k); y=find(root,k+tot+1);
	splay(x,root); splay(y,c[x][1]);
	z=c[y][0]; reclaim(z); c[y][0]=0;
	pushup(y); pushup(x);
}

void solve_change(int k,int val){
	v[k]=val; tag[k]=1;//tag相当于lazy操作
	sum[k]=v[k]*siz[k];
	if(val>=0)lmx[k]=rmx[k]=mx[k]=sum[k];
	else lmx[k]=rmx[k]=0,mx[k]=v[k];
}

void change(int k,int tot,int val){
	int x,y,z;
	x=find(root,k); y=find(root,k+tot+1);
	splay(x,root); splay(y,c[x][1]);
	z=c[y][0];
	solve_change(z,val);
	pushup(y); pushup(x);
}

void solve_rever(int k){
	if(tag[k])return ;//已经将该区间的数全部变为相等的数,反转就没有意义
	rev[k]^=1;//rev标记是否要反转 
	swap(c[k][0],c[k][1]);
	swap(lmx[k],rmx[k]);
}

void rever(int k,int tot){
	int x,y,z;
	x=find(root,k); y=find(root,k+tot+1);
	splay(x,root); splay(y,c[x][1]);
	z=c[y][0];
	solve_rever(z);
	pushup(y); pushup(x);//注意先更新y再更新x(因为此时y为x的右儿子)
}

void query(int k,int tot){
	int x,y,z;
	x=find(root,k); y=find(root,k+tot+1);
	splay(x,root); splay(y,c[x][1]);
	z=c[y][0];
	printf("%d\n",sum[z]);
}

int main(){
	scanf("%d%d",&N,&M);
	mx[0]=-inf;
	a[1]=a[N+2]=-inf;
	for(int i=1;i<=N;i++)scanf("%d",&a[i+1]);
	for(int i=1;i<=N+2;i++)id[i]=i;
	built(1,N+2,0);//建立1和N+2这两个虚拟节点
	root=(N+3)>>1; size=N+2;
	char s[10];
	int k,tot,val;
	for(int i=1;i<=M;i++){
		scanf("%s",s);
		switch(s[0]){
			case 'I':scanf("%d",&k);scanf("%d",&tot);ins(k,tot);break;
			case 'D':scanf("%d",&k);scanf("%d",&tot);del(k,tot);break;
			case 'M':
				if(s[2]=='K'){
					scanf("%d%d%d",&k,&tot,&val); change(k,tot,val);
				}
				else printf("%d\n",mx[root]);
				break;
			case 'R':scanf("%d%d",&k,&tot);rever(k,tot);break;
			case 'G':scanf("%d%d",&k,&tot);query(k,tot);break;
		}
	}
	return 0;
}


评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值