删除操作还没有验证,原本例题是HYSBZ5084,不过网站好像炸了,一直没法提交,目前不知道删除代码正确性,不过插入和维护height都已经验证过
意思就是你可能会被这篇文章演
平衡树用treap写的
什么是后缀平衡树?
利用平衡树动态维护后缀数组。
可以支持的操作为加入一个字符到串末尾或者删除末尾一个字符。
先搞清两个问题:
只能在末尾操作吗?
中间插入删除会影响多个后缀,末尾插入删除只影响一个后缀所以可以维护。
为什么字符串末尾添加一个字符只影响一个后缀,因为在我们的理解中,添加一个字符不就是在之前每一个后缀后面都加了一个字符吗,这样每个后缀大小都变了啊?
因为其实我们维护的是前缀(也就是网上说的后缀平衡树只支持前段加入一个字符)
比如我们插入ABBCD,一次插入的话,我们维护的是:A,BA,BBA,CBBA,DCBBA
维护这样的前缀,我们就可以保证新插入一个字符,只多了一个新的后缀,不影响之前所有的后缀的大小关系。
相应的,之前height维护最长相同前缀,现在求height需要从末尾往前求,因为末尾才是一个新串的开头
后缀大小比较方法
想要新插入一个后缀,我们必须要能够比较出新后缀和老后缀的大小。
方法一:
二分+hash求两个节点最长相同前缀,然后下一位就能比较出大小,求LCP复杂度logn,算上插入,复杂度O(logn*logn)
方法二:
新插入的后缀由一个新加入的字符作为开头,后面一坨都是平衡树中原有的,我们给平衡树中每个节点都搞个tag值,用于直接表示这个后缀的大小。
新后缀和其他节点比较时,只需比较两者的第一个字符,以及两者去掉首字符对应后缀的tag即可。
这个方法比较复杂度为O(1),算上插入,复杂度为O(logn)
方法二的具体解释:
相当于线段树一样,每个点有一个l,r,该点的tag = (l+r)/2,左儿子的l ,r分别等于当前节点的l,tag,右儿子的l,r分别等于当前节点的tag,r。
插入当前的后缀后,父节点就可以搞好当前节点的l,r和tag
之后不同平衡树由于要维持平衡各自有不同的操作(我只会treap,其他不了解)
比如说旋转,树的父子关系发生变化,这个时候可咋办呀?直接整棵子树暴力重新搞tag!
这个复杂度想想是挺玄学的,听说是均摊O(1)
直接扔一坨代码,稍微改改可以过这题(指多组输入)
删除相关函数真的不确定对了没有!
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define for1(i,a,b) for (int i=a;i<=b;i++)
#define for0(i,a,b) for (int i=a;i<b;i++)
#define debug(x) printf("---- %s ----\n",#x)
#define INF 1.00
const int N = 1e5+5;
char op[N];
struct SAT
{
int ch[N][2];
int pri[N];
double l[N],r[N],tag[N];//维护tag需要的
int height[N];
int pre[N],suc[N];//前驱后继,相当于后缀数组sa[]数组
int fa[N];//储存当前节点的父节点
int sz,root;
ll ans;
int s[N];//储存插入的串
unsigned long long hash[2][N],power[2][N],mi[2]={97,311};//双哈希,就是两遍哈希
void init(){//初始化
sz = 1;
ans = root = 0;
power[0][0] = power[1][0] = 1;
for (int i=1;i<=1000;i++) power[0][i] = power[0][i-1]*mi[0], power[1][i] = power[1][i-1]*mi[1];
}
int rand(){//优化rand()
static int seed=703; ///static 起到全局变量的效果
return seed=int(seed*48271LL%2147483647);///48271,2147483647这两个数字应该很关键,然后longlong转int也很关键
}
int newnode(double L,double R){//新建节点
ch[sz][0] = ch[sz][1] = 0;
pri[sz] = rand();
l[sz] = L,r[sz] = R,tag[sz] = (L+R)/2;
pre[sz] = suc[sz] = fa[sz] = height[sz] = 0;
return sz++;
}
bool cmp_hash(int a,int aa,int b,int bb){//判断串1的a~aa位和串2的b~bb位是否相等
if (hash[0][aa]-hash[0][a-1]*power[0][aa-a+1] != hash[0][bb]-hash[0][b-1]*power[0][bb-b+1]) return false;
if (hash[1][aa]-hash[1][a-1]*power[1][aa-a+1] != hash[1][bb]-hash[1][b-1]*power[1][bb-b+1]) return false;
return true;
}
int lcp(int x,int y){//二分求两者的LCP,用于维护height
int l = 0,r = min(x,y);
while (l<=r){
int p = l+r;//x-p+1~x,y-p+1~y
bool flag = true;
if (cmp_hash(x-p+1,x,y-p+1,y)) l = p+1;
else r = p-1;
}
//printf("lcp(%d,%d) = %d\n",x,y,r);
return r;
}
void maintain_height(int op){//维护height数组,op=0表示添加一个字符后维护height,op=1表示删除一个字符维护height
//printf("before:ans = %d\n",ans);
int now = sz-1;
int bef = pre[now];
int aft = suc[now];
if (op==0){//插入了sz-1节点
ans -= aft - height[aft];
height[now] = lcp(now,bef);
height[aft] = lcp(aft,now);
ans += now -height[now] + aft - height[aft];
}
else {
ans -= aft-height[aft] + now-height[now];
height[aft] = lcp(bef,aft);
ans += aft-height[aft];
}
//printf("after:ans = %d\n",ans);
}
void rebuild(int rt,double L,double R){//暴力重构整棵子树的tag
double mid = (L+R)/2;
if (ch[rt][0]) rebuild(ch[rt][0],L,mid);
l[rt] = L,r[rt] = R,tag[rt] = mid;
if (ch[rt][1]) rebuild(ch[rt][1],mid,R);
}
void rotate(int& rt,int d){//旋转
double L = l[rt],R = r[rt];
fa[ch[rt][d^1]] = fa[rt];//旋转要改变父子关系
fa[rt] = ch[rt][d^1];
int k = ch[rt][d^1];//下面四行就是旋转的基本操作
ch[rt][d^1] = ch[k][d];
ch[k][d] = rt;
rt = k;
rebuild(rt,L,R);//每次旋转都要重构子树
}
void insert(int f,int& rt,int p,int dd){//dd=0表示作为f的左儿子,dd=1表示作为f的右儿子,p是新插入节点的下标
if (!rt){
if (!dd) {
rt = newnode(l[f],tag[f]);
suc[rt] = f;
pre[rt] = pre[f];
if (f) pre[f] = rt;
if (pre[rt]) suc[pre[rt]] = rt;
}
else {
rt = newnode(tag[f],r[f]);
pre[rt] = f;
suc[rt] = suc[f];
if (f) suc[f] = rt;
if (suc[rt]) pre[suc[rt]] = rt;
}
fa[rt] = f;
}
else {
int d = (s[p]<s[rt] || s[p]==s[rt] && tag[p-1]<tag[rt-1])? 0:1;//比较两个后缀的大小
insert(rt,ch[rt][d],p,d);
if (pri[ch[rt][d]] > pri[rt]) rotate(rt,d^1);///***维护大根堆
}
}
void add(char c){
int now = sz;
s[now] = c;
hash[0][now] = hash[0][now-1]*mi[0] + s[now];
hash[1][now] = hash[1][now-1]*mi[1] + s[now];
if (now==1){//第一个节点特殊处理
root = 1;
newnode(0,INF);
fa[1] = 0;
}
else{//因为insert函数那么写了,需要那么几个参数,这里只能这么写了,码量有点大
int d = (s[now]<s[root] || s[now]==s[root] && tag[now-1]<tag[root-1])? 0:1;
insert(root,ch[root][d],now,d);
if (pri[ch[root][d]] > pri[root]) rotate(root,d^1);
}
maintain_height(0);
}
int merge(int x,int y){//合并:类似无旋treap的merge,用于删除时把要删除的节点的两个儿子合并
if (!x || !y) return x+y;
else {
if (pri[x]>pri[y]){
ch[x][1] = merge(ch[x][1],y);
fa[ch[x][1]] = x;
return x;
}
else {
ch[y][0] = merge(x,ch[y][0]);
fa[ch[y][0]] = y;
return y;
}
}
}
void remove(){//删掉当前末节点
maintain_height(1);
suc[pre[sz-1]] = suc[sz-1];
pre[suc[sz-1]] = pre[sz-1];
double lll = l[sz-1],rrr = r[sz-1];
if (root == sz-1){//如果删除的是根节点,需要重新赋值根节点
root = merge(ch[sz-1][0],ch[sz-1][1]);
rebuild(root,lll,rrr);
}
else {
int f = fa[sz-1];
int d = ch[f][0]==sz-1? 0:1;
int rt = merge(ch[sz-1][0],ch[sz-1][1]);
rebuild(root,lll,rrr);
fa[rt] = f;
ch[f][d] = rt;
}
sz--;
}
/*
void DFS(){dfs(root);puts("");}
void dfs(int now){
if (ch[now][0]) dfs(ch[now][0]);
printf("now = %d:",now);
for (int i=now;i>=1;i--) printf("%c",s[i]);
puts("");
//printf("pre[%d]=%d,suc[%d]=%d\n",now,pre[now],now,suc[now]);
//printf("pri[%d]=%d,tag[%d]=%.6f\n",now,pri[now],now,tag[now]);
if (ch[now][1]) dfs(ch[now][1]);
}
*/
}sat;
#define R sat.root
int main()
{
//freopen("C:/Users/DELL/Desktop/input.txt", "r" ,stdin);
//freopen("C:/Users/DELL/Desktop/myoutput.txt", "w" ,stdout);
//int T;
//scanf("%d",&T);
//while (T--){
sat.init();
scanf("%s",op);
int cnt = strlen(op);
for0(i,0,cnt){
if (op[i]=='-'){
sat.remove();
}
else {
sat.add(op[i]);
}
/*
puts("After every option:");
sat.DFS();
*/
printf("%d\n",sat.ans);
}
//}
return 0;
}
[点击并拖拽以移动]