题解——洛谷P1383 高级打字机

原题链接

 原题链接

算法概述

  读完题目,显然可以用主席树做。

  把所有的插入与撤销操作看成一个序列,按其操作顺序编号,则下标最多为1~n(因为有查询操作的存在,下标实际上不可能到n)。

  在下标上建立主席树。

  每个节点记录其所代表的下标区间内已经插入的字母个数。特别地,叶子节点需要额外记录其下标所对应的操作插入的字母。鉴于本人的字符恐惧症,字母信息直接用数字1~26代替。

  对于插入操作,直接在当前这个操作编号上插入一个字母即可,同时维护区间内字母个数。

  对于撤销操作,假设撤销x步,那就是直接回到x步前的历史版本。

  对于查询操作,假设是查询当前版本的第k个字母,基于线段树的二分结构,对当前所查询到的节点p来说,设其左儿子为L,右儿子为R,节点信息中字母个数为size。若k<=L.size,则递归左儿子,查询第k个字母;否则递归右儿子,查询第k-L.size个字母。

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=1e5+10;

struct Node{
	int l,r;
	int siz;
	int dat;
	#define l(p) tree[p].l
	#define r(p) tree[p].r
	#define siz(p) tree[p].siz
	#define dat(p) tree[p].dat
}tree[N*21];int idx;
int root[N];
int n;

int build(int l,int r)
{
	int p=++idx;
	if(l==r)return p;
	int mid=l+r>>1;
	l(p)=build(l,mid);
	r(p)=build(mid+1,r);
	return p;
}

int insert(int now,int l,int r,int pos,int ch)
{
	int p=++idx;
	tree[p]=tree[now];
	if(l==r)
	{
		dat(p)=ch;
		siz(p)=1;
		return p;
	}
	int mid=l+r>>1;
	if(pos<=mid)l(p)=insert(l(now),l,mid,pos,ch);
	else r(p)=insert(r(now),mid+1,r,pos,ch);
	siz(p)=siz(l(p))+siz(r(p));
	return p; 
}

int query(int p,int l,int r,int x)
{
	if(l==r)return dat(p);
	int mid=l+r>>1;
	if(x<=siz(l(p)))return query(l(p),l,mid,x);
	else return query(r(p),mid+1,r,x-siz(l(p)));
}

int main()
{
	scanf("%d",&n);
	int cnt=0;
	for(int i=1;i<=n;i++)
	{
		char tp[2];
		scanf("%s",tp);
		if(tp[0]=='T')
		{
			cnt++;
			char x;cin>>x;
			root[cnt]=insert(root[cnt-1],1,n,cnt,x-'a'+1);
		}
		else if(tp[0]=='U')
		{
			cnt++;
			int x;scanf("%d",&x);
			root[cnt]=root[cnt-1-x]; 
		}
		else
		{
			int x;scanf("%d",&x);
			char ans=query(root[cnt],1,n,x)-1+'a';
			printf("%c
",ans);
		}
	}
	return 0;
}

  

原文地址:https://www.cnblogs.com/ninedream/p/12891511.html