「Loj120」持久化序列 - Splay+离线

题目描述

这是一道模板题。

您需要维护一个序列,其中需要提供以下操作:

  1. 插入一个数到序列的第 t t t 版本使其成为序列的第 k k k 项,这个数为 x x x
  2. 删除序列的第 t t t 版本的第 k k k 项;
  3. 查询序列的第 t t t 版本的第 k k k 项。

0 0 0 个版本为空序列。修改操作不会影响被修改的版本,而总是产生一个新版本。

输入格式

第一行有一个正整数 n n n 表示操作的数量。

接下来 n n n 行每行第一个正整数 o p t opt opt 表示操作的类型,后面有 3 3 3 个整数 t , k , x t, k, x t,k,x 2 2 2 个整数 t , k t, k t,k 表示操作的参数。

输出格式

对于每个查询操作输出一行一个数,表示查询的结果。

分析

这是一道可持久化平衡树的板子题,但可惜它没有强制在线,用离线的方法水过了。

对于可持久化的题,由于每个版本由且仅由一个版本演变过来的,如果我们把那个版本看作新的版本的父亲,那么这就可以看作是一棵树。我们可以离线所有的操作,构建这样一棵树。对于其中的一个节点,这个节点的操作只会对它的子树产生影响。所以我们可以用一个可撤销的数据结构,在进入这个节点的子树时做其对应的操作,离开时撤销之前的操作,像线段树分治一样,这样就可以做到可持久化的效果。

而可撤销的数据结构,这里可以使用普通维护区间的Splay,好像块链也可以。在进入子树之前,将其对应操作进行修改:加入即为加入,删除时记下来删除的节点的值,然后就直接删除;撤销时,若该点为添加操作,则将其删除,若为删除操作,则进行添加,撤销当时该边的值。

代码其实也很简单。

代码

#include <iostream>
#include <cstdio>
using namespace std;
const int N=600005;
//由于只有修改操作才会新建版本,所以对于查询操作,直接用链表记录 
struct Query {int k,id,nxt;}q[N];
struct Edge {int to,nxt;}e[N];
int h[N],cnt;
int op[N],qk[N],qx[N];
int head[N],tot;
void Add_Edge(int x,int y) {
	e[++cnt]=(Edge){y,h[x]};
	h[x]=cnt;
}
void Add_Query(int x,int y,int id) {
	q[++tot]=(Query){y,id,head[x]};
	head[x]=tot;
}
int n,qtot,ans[N],num;
//Splay基本操作 
int f[N],c[N][2],v[N];
int sz[N],rt,ntot;
void pushup(int x) {sz[x]=sz[c[x][0]]+sz[c[x][1]]+1;}
void Rotate(int x) {
	int y=f[x],z=f[y];
	int k=c[y][1]==x,w=c[x][k^1];
	if (z) c[z][c[z][1]==y]=x;
	f[y]=x;f[x]=z;if (w) f[w]=y;
	c[x][k^1]=y;c[y][k]=w;
	pushup(y);
	pushup(x);
}
void Splay(int x,int to=0) {
	if (!x) return;
	for (int y,z;z=f[y=f[x]],y!=to;Rotate(x))
		if (z!=to) Rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
	if (!to) rt=x;
}
int Find(int k) {
	int p=rt;
	while (p) {
		if (sz[c[p][0]]+1==k) {
			Splay(p);
			return p;
		}
		if (sz[c[p][0]]>=k) p=c[p][0];
		else k-=sz[c[p][0]]+1,p=c[p][1];
	}
	return 0;
}
void Insert(int k,int val) {
	if (k==1) {
		int x=Find(1);
		Splay(x);ntot++;v[ntot]=val;
		sz[ntot]=1;f[ntot]=x;c[x][0]=ntot;
		if (!x) rt=ntot;
		else pushup(rt);
		return;
	}
	if (k==sz[rt]+1) {
		int x=Find(sz[rt]);
		Splay(x);ntot++;v[ntot]=val;
		sz[ntot]=1;c[x][1]=ntot;f[ntot]=x;
		if (!x) rt=ntot;
		else pushup(x);
		return;
	}
	int x=Find(k-1),y=Find(k);
	Splay(x);Splay(y,x);ntot++;
	v[ntot]=val;sz[ntot]=1;c[c[x][1]][0]=ntot;
	f[ntot]=c[x][1];
	pushup(c[x][1]);
	pushup(x);
}
void Delete(int k) {
	if (k==1) {
		int x=Find(1);
		Splay(x);rt=c[x][1];
		f[c[x][1]]=0;c[x][1]=0;
		return;
	}
	if (k==sz[rt]) {
		int x=Find(sz[rt]);
		Splay(x);rt=c[x][0];
		f[c[x][0]]=0;c[x][0]=0;
		return;
	}
	int x=Find(k),y=Find(k-1);
	Splay(x);
	Splay(y,x);
	f[c[x][0]]=0;
	f[c[x][1]]=c[x][0];
	c[c[x][0]][1]=c[x][1];
	rt=c[x][0];
	pushup(c[x][0]);
}
int Get(int k) {return v[Find(k)];}
//End
void Solve(int x) {
	int dt;
	if (op[x]==1) Insert(qk[x],qx[x]);//直接改 
	else dt=Get(qk[x]), Delete(qk[x]);
	for (int i=head[x];i;i=q[i].nxt)
		ans[q[i].id]=Get(q[i].k);
	for (int i=h[x];i;i=e[i].nxt)
		Solve(e[i].to);
	if (op[x]==1) Delete(qk[x]);//撤销 
	else Insert(qk[x],dt);
}
int main() {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		int opr,t,k,x;
		scanf("%d%d%d",&opr,&t,&k);
		if (opr==1) scanf("%d",&x);
		if (opr==1) {//1,2为修改操作,要新建版本,它继承的版本向它连边 
			qtot++;op[qtot]=opr;
			qk[qtot]=k;qx[qtot]=x;
			Add_Edge(t,qtot);
		} else if (opr==2) {
			qtot++;op[qtot]=opr;
			qk[qtot]=k;
			Add_Edge(t,qtot);
		} else if (opr==3) Add_Query(t,k,++num);//3为查询操作,直接加入链表里 
	}
	Solve(0);
	for (int i=1;i<=num;i++)
		printf("%d\n",ans[i]);
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值