bzoj3196/洛谷P3380 线段树套splay 【附赠数据生成器】

假标题:震惊!一只蒟蒻两天刷新两次最长代码记录,这究竟是数据结构的扭曲还是代码能力的沦丧?

题目分析

线段树,套splay嘛。如题所言,我们就弄一棵线段树,然后线段树的每一个节点都是一棵splay,这样就方便完成各种操作。
那么这道题(应该)唯一要讲解的就是第二个操作啦,第二个操作就是二分答案+第一个操作。其他的让代码来解答吧。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=50005,N1=N*80,inf=INT_MAX;
int n,m,cur,lim1,lim2;
int a[N],b[N],c[N],rt[N<<2];//rt:splay的根
int son[N1][2],f[N1],sz[N1],v[N1];
stack<int> st;//垃圾回收
//以下是splay部分
int newjd() {//开一个新的splay节点
	if(st.empty()) return ++cur;
	else {int re=st.top();st.pop();return re;}
}
void up(int x) {sz[x]=sz[son[x][0]]+sz[son[x][1]]+1;}
int is(int x) {return son[f[x]][1]==x;}
void spin(int x,int &mb) {
	int fa=f[x],g=f[fa],t=is(x);
	if(fa!=mb) son[g][is(fa)]=x;
	else mb=x;
	f[fa]=x,f[x]=g,f[son[x][t^1]]=fa;
	son[fa][t]=son[x][t^1],son[x][t^1]=fa;
	up(fa),up(x);
}
void splay(int x,int &mb) {
	while(x!=mb) {
		if(f[x]!=mb) {
			if(is(x)^is(f[x])) spin(x,mb);
			else spin(f[x],mb);
		}
		spin(x,mb);
	}
}
int sbuild(int l,int r,int fa) {//新建一棵splay
	if(l>r) return 0;
	int x=newjd(),mid=(l+r)>>1;
	if(l!=r) son[x][0]=sbuild(l,mid-1,x),son[x][1]=sbuild(mid+1,r,x);
	f[x]=fa,v[x]=a[mid],up(x);
	return x;
}
int sask(int x,int num) {
	int re=0;
	while(x)
		if(v[x]<num) re+=sz[son[x][0]]+1,x=son[x][1];
		else x=son[x][0];
	return re-1;//有哨兵节点
}
int find(int x,int num) {//寻找v值为num的节点
	if(v[x]==num) return x;
	if(v[x]>num) return find(son[x][0],num);
	return find(son[x][1],num);
}
void del(int i,int num) {//删除以rt[i]为根的splay中一个v值为num的节点
	int x=find(rt[i],num); splay(x,rt[i]);
	if(son[x][0]*son[x][1]==0) rt[i]=son[x][0]+son[x][1];
	else {
		int y=son[x][1];
		while(son[y][0]) y=son[y][0];
		f[son[x][0]]=y,son[y][0]=son[x][0],rt[i]=son[x][1];
		while(y) up(y),y=f[y];
	}
	son[x][0]=son[x][1]=f[x]=v[x]=sz[x]=0,st.push(x),f[rt[i]]=0;
}
void ins(int &x,int num,int pre) {
	if(!x) {x=newjd(),v[x]=num,f[x]=pre,sz[x]=1;return;}
	if(num<=v[x]) ins(son[x][0],num,x);
	else ins(son[x][1],num,x);
	up(x);
}
int spre(int x,int num) {
	if(!x) return  -inf;
	if(num<=v[x]) return spre(son[x][0],num);
	return max(spre(son[x][1],num),v[x]);
}
int snxt(int x,int num) {
	if(!x) return inf;
	if(num>=v[x]) return snxt(son[x][1],num);
	return min(snxt(son[x][0],num),v[x]);
}
//以下是线段树部分
void build(int s,int t,int i) {//建树
	if(s==t) {
		int t1=a[s-1],t2=a[t+1];//添加哨兵节点
		a[s-1]=-inf,a[t+1]=inf;
		rt[i]=sbuild(s-1,t+1,0);
		a[s-1]=t1,a[t+1]=t2;return;
	}
	int mid=(s+t)>>1;
	build(s,mid,i<<1),build(mid+1,t,(i<<1)|1);
	//以下是归并排序
	int k1=s,k2=mid+1,k3=0,t1=a[s-1],t2=a[t+1];
	a[s-1]=-inf,a[t+1]=inf;
	while(k1<=mid&&k2<=t)
		if(a[k1]<=a[k2]) b[++k3]=a[k1],++k1;
		else b[++k3]=a[k2],++k2;
	while(k1<=mid) b[++k3]=a[k1],++k1;
	while(k2<=t) b[++k3]=a[k2],++k2;
	for(int j=1;j<=k3;++j) a[s+j-1]=b[j];
	rt[i]=sbuild(s-1,t+1,0),a[s-1]=t1,a[t+1]=t2;
}
int ask(int l,int r,int s,int t,int i,int num) {
	if(l<=s&&t<=r) return sask(rt[i],num);
	int mid=(s+t)>>1,re=0;
	if(l<=mid) re=ask(l,r,s,mid,i<<1,num);
	if(mid+1<=r) re+=ask(l,r,mid+1,t,(i<<1)|1,num);
	return re;
}
void chan(int x,int s,int t,int i,int num) {
	del(i,c[x]),ins(rt[i],num,0);
	if(s==t) {c[x]=num;return;}
	int mid=(s+t)>>1;
	if(x<=mid) chan(x,s,mid,i<<1,num);
	else chan(x,mid+1,t,(i<<1)|1,num);
}
int pre(int l,int r,int s,int t,int i,int num) {
	if(l<=s&&t<=r) return spre(rt[i],num);
	int mid=(s+t)>>1,re=-inf;
	if(l<=mid) re=pre(l,r,s,mid,i<<1,num);
	if(mid+1<=r) re=max(re,pre(l,r,mid+1,t,(i<<1)|1,num));
	return re;
}
int nxt(int l,int r,int s,int t,int i,int num) {
	if(l<=s&&t<=r) return snxt(rt[i],num);
	int mid=(s+t)>>1,re=inf;
	if(l<=mid) re=nxt(l,r,s,mid,i<<1,num);
	if(mid+1<=r) re=min(re,nxt(l,r,mid+1,t,(i<<1)|1,num));
	return re;
}
//二分答案
void work(int x,int y,int z) {
	int l=lim1,r=lim2,mid,re;
	while(l<=r) {
		mid=(l+r)>>1;
		if(ask(x,y,1,n,1,mid)>=z) r=mid-1;
		else re=mid,l=mid+1;
	}
	printf("%d\n",re);
}
int main()
{
	int bj,x,y,z;
	scanf("%d%d",&n,&m),lim1=inf,lim2=-inf;
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]),lim1=min(lim1,a[i]),lim2=max(lim2,a[i]),c[i]=a[i];
	build(1,n,1);
	while(m--) {
		scanf("%d%d%d",&bj,&x,&y);
		if(bj==1) scanf("%d",&z),printf("%d\n",ask(x,y,1,n,1,z)+1);
		else if(bj==2) scanf("%d",&z),work(x,y,z);
		else if(bj==3) lim1=min(lim1,y),lim2=max(lim2,y),chan(x,1,n,1,y);
		else if(bj==4) scanf("%d",&z),printf("%d\n",pre(x,y,1,n,1,z));
		else if(bj==5) scanf("%d",&z),printf("%d\n",nxt(x,y,1,n,1,z));
	}
	return 0;
}

数据生成器

#include<bits/stdc++.h>
using namespace std;
int n,m;
int main()
{
    srand(time(0));
    n=rand()%10+1,m=rand()%10+1,printf("%d %d\n",n,m);
    for(int i=1;i<=n;++i) printf("%d ",rand()%100);
    puts("");
    for(int i=1;i<=m;++i) {
    	int bj=rand()%5+1;
    	printf("%d ",bj);
    	if(bj==3) printf("%d %d\n",rand()%n+1,rand()%100);
    	else {
    		int r=rand()%n+1,l=rand()%r+1;
    		printf("%d %d ",l,r);
    		if(bj==2) printf("%d\n",rand()%(r-l+1)+1);
			else printf("%d\n",rand()%100);
    	}
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值