可持久化线段树(主席树)
其实这个模板我就花了四个晚上调试,所以代码实现能力弱是个大坑:)
思路
主席树的另一个名字叫做可持久化权值线段树,用于维护多个版本的线段树。由于开多颗线段树的话空间会炸到飞起,所以可以充分利用每棵线段树中的重复部分。比如在第i + 1的版本的末尾新增一个节点,第i + 1棵版本的线段树和第i棵线段树的区别只是rt的右儿子及其子树中的一小部分。我们就可以在每一次更新版本时,另开那些有变化的节点,并与没有变化的节点连边(我们把这称之为动态开点)。这样即可节省大量空间,查询仿照普通线段树即可,
注意事项
1、有些题目一开始一定要开一棵空树;
2、要把每一个版本的根节点开个数组存起来;
JZOJ 3794 高级打字机
Description
早苗入手了最新的高级打字机。最新款自然有着与以往不同的功能,那就是它具备撤销功能,厉害吧。
请为这种高级打字机设计一个程序,支持如下3种操作:
T x:在文章末尾打下一个小写字母x。(type操作)
U x:撤销最后的x次修改操作。(Undo操作)(注意Query操作并不算修改操作)
Q x:询问当前文章中第x个字母并输出。(Query操作)文章一开始可以视为空串。
Input
第1行:一个整数n,表示操作数量。以下n行,每行一个命令。保证输入的命令合法。
Output
每行输出一个字母,表示Query操作的答案。
Sample Input
7
T a
T b
T c
Q 2
U 2
T c
Q 2
Sample Output
bc
Data Constraint
n<=100000;Undo操作可以撤销Undo操作。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 10000007;
int n, tot = 0, cnt = 0, t;
int rt[N], lson[N], rson[N], len[N];
char v[N], c;
void add(int &cur, int po, int l, int r, int last)
{
if (!cur) cur = ++tot;
if (l == r)
{
v[cur] = c;
return;
}
int mid = (l + r) >> 1;
if (po <= mid)
rson[cur] = rson[last], add(lson[cur], po, l, mid, lson[last]);
if (mid + 1 <= po)
lson[cur] = lson[last], add(rson[cur], po, mid + 1, r, rson[last]);
}
char ask(int po, int cur, int l, int r)
{
if (l == r)
return v[cur];
int mid = (l + r) >> 1;
if (po <= mid)
return ask(po, lson[cur], l, mid);
if (mid + 1 <= po)
return ask(po, rson[cur], mid + 1, r);
}
int main()
{
scanf("%d", &n);
memset(rt, 0, sizeof rt);
memset(lson, 0, sizeof lson);
memset(rson, 0, sizeof rson);
memset(len, 0, sizeof len);
for (int i = 1; i <= n; i++)
{
char type;
scanf(" %c", &type);
if (type == 'T')
{
scanf(" %c", &c);
len[++cnt] = len[cnt - 1] + 1;
add(rt[cnt], len[cnt], 1, N, rt[cnt - 1]);//r在这道题中一定要是常数N哦,不然不变化的节点所代表的区间就不一样了
}
if (type == 'U')
{
scanf("%d", &t);
len[++cnt] = len[cnt - t - 1];
rt[cnt] = rt[cnt - t - 1];
}
if (type == 'Q')
{
scanf("%d", &t);
printf("%c\n", ask(t, rt[cnt], 1, N));
}
}
return 0;
}