后缀数组+平衡树=后缀平衡树
支持动态插入字符(只能往前插入),即插入一个后缀,维护所有后缀的排名
插入后缀找到位置?平衡树上二分
法一:
哈希+二分,太慢
法二:
第一个字符不同,已经可以比较,否则比较第二个字符开始的后缀,之前这两个后缀排名已经处理好了。直接比较排名即可。
查询任意位置的排名?
法一:
暴力这个点往上跳log次,找到rank
法二:
每个点打一个tag,把排名“绝对化”
记录[l,r],val,表示x子树的tag值域区间和x的值val,val=(l+r)/2
这样可以O(1)查排名
总值域是[1,inf],深度logn,所以开个double一定精度没有问题。
具体用重量平衡树维护(子树大小期望logn,如treap或者SGT),SGT就可以啦
这样直接重构的时候把[l,r]tag重新赋值。(treap旋转时候必须直接重构。否则tag就乱了)
优点:
相较于LCT+SAM,可以离线,可以可持久化
例题
都挺裸的。。。
bzoj3682: Phorni
线段树维护区间字典序最小的id即可
注意:
由于tag或者排名是不断变化的,不能记录tag的值,只要记录id是哪一个即可。
因为新插入后缀,相对排名不影响
改变某一个pos[x]只会影响到根的结果。
pushup时候现场比较
#include<bits/stdc++.h> #define reg register int #define il inline #define fi first #define se second #define mk(a,b) make_pair(a,b) #define numb (ch^'0') #define pb push_back #define solid const auto & #define enter cout<<endl #define pii pair<int,int> using namespace std; typedef long long ll; template<class T>il void rd(T &x){ char ch;x=0;bool fl=false; while(!isdigit(ch=getchar()))(ch=='-')&&(fl=true); for(x=numb;isdigit(ch=getchar());x=x*10+numb); (fl==true)&&(x=-x); } template<class T>il void output(T x){if(x/10)output(x/10);putchar(x%10+'0');} template<class T>il void ot(T x){if(x<0) putchar('-'),x=-x;output(x);putchar(' ');} template<class T>il void prt(T a[],int st,int nd){for(reg i=st;i<=nd;++i) ot(a[i]);putchar('\n');} namespace Miracle{ const int N=500000+5; const int M=1000000+5; const double inf=1e18; const double alp=0.75; #define mid ((l+r)>>1) #define Md ((L+R)/2.0) int n,q,m,typ; int pos[N]; char s[M]; int rt; namespace SGT{ struct node{ int ch[2]; int sz; double l,r; double val; void init(double L,double R){ sz=1;ch[0]=ch[1]=0;l=L;r=R;val=(L+R)/2; } }t[M]; int tot; #define ls t[x].ch[0] #define rs t[x].ch[1] void pushup(int x){ if(!x) return; t[x].sz=t[ls].sz+t[rs].sz+1; } bool isbad(int x){ return (t[ls].sz>t[x].sz*alp||t[rs].sz>t[x].sz*alp); } int q[M],num; void dfs(int x){ if(!x) return; dfs(ls); q[++num]=x; dfs(rs); } int build(int l,int r,double L,double R){ if(l>r) return 0; int x=q[mid]; t[x].init(L,R); ls=build(l,mid-1,L,Md); rs=build(mid+1,r,Md,R); pushup(x); return x; } void rebuild(int &x){ num=0;dfs(x); x=build(1,num,t[x].l,t[x].r); } int sta[M],top; void che(){ for(reg i=1;i<=top;++i){ if(isbad(sta[i])){ if(i==1) rebuild(rt); else rebuild(t[sta[i-1]].ch[t[sta[i-1]].ch[1]==sta[i]]),pushup(sta[i-1]); return; } } } bool cmp(int x,int y){//x<y? if(s[x]!=s[y]) return s[x]<s[y]; return t[x-1].val<t[y-1].val; } void ins(int p){//x is pos ++tot; if(!rt){ rt=tot; t[tot].init(1,inf); return; } int x=rt; top=0; while(1){ int d=cmp(x,p); sta[++top]=x; ++t[x].sz; if(!t[x].ch[d]){ t[x].ch[d]=tot; if(d==1){//rson t[tot].init(t[x].val,t[x].r); }else{ t[tot].init(t[x].l,t[x].val); } break; } x=t[x].ch[d]; } che(); } void pb(int x){ ins(x); } #undef ls #undef rs } namespace SMT{ #define ls (x<<1) #define rs (x<<1|1) struct node{ int id; }t[4*N]; int chm(int x,int y){ return SGT::t[pos[x]].val<=SGT::t[pos[y]].val?x:y; } void pushup(int x){ t[x].id=chm(t[ls].id,t[rs].id); } void build(int x,int l,int r){ if(l==r){ t[x].id=l;return; } build(ls,l,mid); build(rs,mid+1,r); pushup(x); } void upda(int x,int l,int r,int p){ if(l==r){ t[x].id=l;return; } if(p<=mid) upda(ls,l,mid,p); else upda(rs,mid+1,r,p); pushup(x); } int query(int x,int l,int r,int L,int R){ if(L<=l&&r<=R){ return t[x].id; } if(L>mid) return query(rs,mid+1,r,L,R); if(R<=mid) return query(ls,l,mid,L,R); return chm(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R)); } } int main(){ rd(n);rd(q);rd(m);rd(typ); scanf("%s",s+1); reverse(s+1,s+m+1); for(reg i=1;i<=n;++i){ rd(pos[i]); } for(reg i=1;i<=m;++i){ SGT::pb(i); } SMT::build(1,1,n); char ch[233]; int x,y; int lasans=0; while(q--){ scanf("%s",ch); if(ch[0]=='I'){ rd(x); if(typ) x=x^lasans; s[++m]='a'+x; SGT::pb(m); }else if(ch[0]=='C'){ rd(x);rd(y); pos[x]=y; SMT::upda(1,1,n,x); }else if(ch[0]=='Q'){//warning!!! rd(x);rd(y); int now=SMT::query(1,1,n,x,y); lasans=now; printf("%d\n",lasans); //update lasans } } return 0; } } signed main(){ Miracle::main(); return 0; } /* Author: *Miracle* */
例题2
维护一个栈,支持末尾添加删除字符
每次询问一个区间中,另一个输入的串的出现次数
• ? ≤ 5 × 10^5, ∑ ? ≤ 5 × 10
一个模式串在主串出现位置,一定是后缀数组sa数组的一段区间
可以在后缀平衡树上找到这个区间
找LCP用哈希+二分
但是是主串的一部分?
差分!变成前缀出现次数相减
可持久化后缀平衡树
但是还有删除字符,所以可持久化是树形结构而不是链,
每次还要倍增找到要差分的节点。
UOJ101
• 维护 ? 个字符串变量,要求支持
• 某个字符串末尾加字符
• 用一个字符串覆盖另一个字符串
• 询问 ? 有几个子串,可通过 ? 时刻的串 ? 在开头加上 ?, ? 内的字符得到
• 询问 ? 有几个子串,可通过给出的串 ? 在开头加上 ?, ? 内的字符得到
• ?, ? ≤ 10^6,强制在线
和上一题差不多
第三第四个询问,就算是加上[l,r]字符,还是一个区间,依然可以暴力二分
还要可持久化
恶心恶心