[BZOJ3682]Phorni (Splay)
题意简述
要求维护一个字符串$S$,支持动态从前端插入字符。
还要求维护一个序列$P$,支持单点修改,区间查询。
区间查询,具体为:对于区间内每一个数$x$,找到字符串$S$长度为$x$的后缀,并返回字典序最小的后缀所对应的$x$对应的$P$的下标$pos$,若有重复输出最小的。
数据范围
$1 ≤ n ≤ 500000, 1 \leq m \leq 800000, 1 \leq Pi \leq L \leq 100000$
字符集大小为$26$
插入前缀操作数量约占总操作数量$\frac{1}{5}$,查询修改操作约占总操作数量$\frac{2}{5}$
题解
这题显然可以分成两部分来做。
对于每个询问和修改,我们可以使用线段树维护后缀$Rank$的最小值,以及出现的位置$pos$。
那么问题主要就是,如何维护$Rank$。
观察发现,此题并不需要维护$Rank$的准确值,只需要维护相对的$Rank$,即后缀的相对大小关系。
因此我们可以使用浮点数来维护$Rank$。
维护相对大小关系,可以使用平衡树维护。
观察发现,每插入一个字符,本质上是多了一个后缀(新的整串$S'$)。
如果我们能在$O(1)$的时间进行后缀大小关系的比较,那么便可在$O(logL)$的时间内完成插入(在平衡树上插入一个新节点)。
之后更新新节点的$Rank$即可完成本次插入。
对于已经在平衡树内的节点,已经有现成的$Rank$数组可以用来比较大小了,因此我们只需要关心,如何让插入的新串和之前已经存在的串比较大小。
如果新串的第一位与旧串的第一位不同,字典序的大小显而易见。
如果新串$S'$的第一位与旧串的第一位相同,我们就应该从第二位比较。
由于新串$S'$是旧串$S$加上一个字符后得到的,所以从新串$S'$第二位开始的字符串,就是之前的旧串$S$。
而旧串$S$之前已经被插入到平衡树内了,可以直接使用$Rank$进行比较。
所以可以在$O(1)$的时间内比较后缀的大小。
因此对于每个插入操作:
- 用$O(1)$的比较函数,在平衡树内找到可以插入的位置,并插入。
- 将插入的新节点旋转到根,并更新新节点的$Rank$
对于新节点的$Rank$,如果它存在前驱和后继,我们就更新其为$\frac{Rank_{pre}+Rank_{nex}}{2}$。
如果其不存在后继,即其是平衡树内最大的节点,我们更新其为$Rank_{pre}+K$
不存在前驱同理,更新为$Rank_{nex}-K$
我们可以注意到,空串是最小的串,但是其对应的$Rank$为0,因此我们不能让任意一个$Rank$小于或等于0。
所以如果一个节点同时不存在前驱和后继,我们更新$Rank$为$C$。
其中$C,K$均为常数,$C,K$差距应不能太大,避免小数出现精度误差。
(我的代码中$C,K$的设定应该是能卡WA的)
当然,也可以在初始时往平衡树内插入一个空串来避免出现没有前驱的情况。
由于每次操作都是除以2,根据浮点数的存储原理,可知不太容易因精度误差卡挂。
代码
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
#include<bitset>
#include<complex>
#include<cmath>
using namespace std;
template<typename __T>
inline void read(__T &x)
{
x=0;
int f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-1;c=getchar();}
while(isdigit(c)) {x=x*10+c-'0';c=getchar();}
x*=f;
}
int rot=0;
double Rank[300005];
int son[300005][2],fa[300005];
int str[300005];
void rotate(int x,int& k)
{
int y=fa[x];
int z=fa[y];
if(y==k) k=x;
else son[z][son[z][1]==y]=x;
int l=(son[y][1]==x);
fa[son[x][!l]]=y;
fa[y]=x;
fa[x]=z;
son[y][l]=son[x][!l];
son[x][!l]=y;
}
void splay(int x,int& k)
{
while(x!=k)
{
int y=fa[x];
int z=fa[y];
if(y!=k)
{
if((son[y][0]==x)^(son[z][0]==y)) rotate(x,k);
else rotate(y,k);
}
rotate(x,k);
}
}
bool cmp(int a,int b)
{
return str[a]<str[b] || (str[a]==str[b] && Rank[a-1]<Rank[b-1]);
}
void ins(int& k,int x,int fai)
{
if(k==0)
{
k=x;
fa[k]=fai;
splay(k,rot);
return;
}
if(cmp(x,k)) ins(son[k][0],x,k);
else ins(son[k][1],x,k);
}
int getpre(int x)//splay before
{
int now=son[x][0];
while(son[now][1]) now=son[now][1];
return now;
}
int getnex(int x)//splay before
{
int now=son[x][1];
while(son[now][0]) now=son[now][0];
return now;
}
void add(int x)
{
ins(rot,x,0);
int a=getpre(x);
int b=getnex(x);
if(a==0 && b!=0)
{
Rank[x]=Rank[b]-1;
return;
}
if(a!=0 && b==0)
{
Rank[x]=Rank[a]+1;
return;
}
if(a==0 && b==0)
{
Rank[x]=(1ll<<10);
return;
}
Rank[x]=(Rank[a]+Rank[b])/2;
}
double p[2000005];
int pos[2000005];
void pushup(int id)
{
if(p[id<<1]<=p[(id<<1)|1])
pos[id]=pos[id<<1];
else
pos[id]=pos[(id<<1)|1];
p[id]=min(p[id<<1],p[(id<<1)|1]);
}
void change(int l,int r,int id,int x,int k)
{
if(l==r)
{
p[id]=Rank[k];
pos[id]=x;
return;
}
int mid=(l+r)>>1;
if(x<=mid) change(l,mid,id<<1,x,k);
else change(mid+1,r,(id<<1)|1,x,k);
pushup(id);
}
pair<int,double> que(int l,int r,int li,int ri,int id)
{
if(l==li && r==ri)
{
return make_pair(pos[id],p[id]);
}
int mid=(l+r)>>1;
if(ri<=mid) return que(l,mid,li,ri,id<<1);
if(li>mid) return que(mid+1,r,li,ri,(id<<1)|1);
pair<int,double>a,b;
a=que(l,mid,li,mid,id<<1);
b=que(mid+1,r,mid+1,ri,(id<<1)|1);
if((a.second)<=(b.second)) return a;
return b;
}
int n,m,len,typ;
char sss[100005];
int main()
{
read(n);
read(m);
read(len);
read(typ);
scanf("%s",sss+1);
for(int i=1;i<=len/2;i++)
swap(sss[i],sss[len+1-i]);
for(int i=1;i<=len;i++)
{
str[i]=sss[i]-'a';
add(i);
}
for(int i=1;i<=n;i++)
{
int x;
read(x);
change(1,n,1,i,x);
}
int ans=0;
while(m--)
{
int t;
scanf("%s",sss);
int a,b;
if(sss[0]=='I')
{
read(a);
if(typ)
a=((a)^ans);
str[++len]=a;
add(len);
}
if(sss[0]=='C')
{
read(a);
read(b);
change(1,n,1,a,b);
}
if(sss[0]=='Q')
{
read(a);
read(b);
ans=que(1,n,a,b,1).first;
printf("%d\n",ans);
}
}
return 0;
}
参考
没有