bzoj 3682: Phorni 后缀平衡树+线段树

4 篇文章 0 订阅
1 篇文章 0 订阅

题意

Phorni 是一个音之妖精,喜欢在你的打字机上跳舞。
一天,阳光映射到刚刚淋浴过小雨的城市上时,Phorni 用魔法分裂出了许多个幻影,从 1 到 n 编号。
她的每一个幻影都站在打出的字符串的一个位置上,多个幻影可以站在同一个位置上。
每一个幻影代表的字符串即为从它站立位置开始的后缀,注意站立位置是从右往左数的。
让我们形式化地描述一下,若第 i 个幻影站在 Pi 上,那么它所代表的字符串就是 S[L-Pi+1…L],其中 L 是字符串 S 的长度。
每一次,她会选一段编号区间 [l..r],而编号在这个区间中的幻影中,字典序最小的一个将跳一支舞,若有多个幻影字典序相同,选编号最小的。
当然由于 Phorni 还会在打字机上跳动,所以有时字符串的前面会加入一个字符。
当然这个打字机是带加密功能的。
字典序的比较:
将两个字符串逐位比较,长度不足的向后补 0 ( 0 小于任何字符) 。直到比出大小或判定相等。
比如 “pho” > “ph” , “pb” > “pab” 。
下标从 1 开始,保证涉及到的所有字符都为小写字母。
对于 100% 的数据, 1 ≤ n ≤ 500000, 1 ≤ m ≤ 800000, 1 ≤ Pi ≤ len ≤ 100000。
若 type = 0,保证 I 操作中 0 ≤ c ≤ 25;否则 0 ≤ c xor lastans ≤ 25 。
C 操作中 1 ≤ x ≤ n, 1 ≤ pos ≤ 当前字符串长度。
Q 操作中 1 ≤ l, r ≤ n,I 操作数量约占总操作数量的 1/5,C,Q 操作数量约各占总操作数量的 2/5。

分析

如果离线的话就可以用sa或sam,但现在是强制在线,就只能用后缀平衡树。
具体的后缀平衡树可以看clj13年的论文。
这题的话,相当于每次加入一个后缀,然后我们将这个后缀插入到后缀平衡树中,找到其对应位置。
对于每个后缀我们都可以通过其在平衡树中的位置确定其rank,这个rank可以通过给每个节点赋一个区间来维护。每次treap旋转的时候暴力修改rank即可。因为据说treap的期望旋转次数非常的小,但我并不会证。当然如果觉得不保险的话写替罪羊树也是资瓷的。
对于两个后缀的比较我们可以通过比较两个后缀的首字母和其下一个位置对应后缀的rank来实现。
这题的话,treap不旋转比旋转还要快。。。显然出题人的数据是随机的。
但为了不被hack我还是加了旋转上去。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=1000005;
const LL inf=(LL)1<<61;

int n,m,len,ty,s[N],rt,mn[N*5],p[N];
LL rank[N];
char str[N];
struct tree{int l,r,k;}t[N];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

bool cmp(int x,int y)
{
    return s[x]<s[y]||s[x]==s[y]&&rank[x-1]<=rank[y-1];
}

void rebuild(int d,LL l,LL r)
{
    LL mid=(l+r)/2;
    rank[d]=mid;
    if (t[d].l) rebuild(t[d].l,l,mid);
    if (t[d].r) rebuild(t[d].r,mid+1,r);
}

void rttl(int &x,LL l,LL r)
{
    int y=t[x].r;
    t[x].r=t[y].l;
    t[y].l=x;
    x=y;
    rebuild(y,l,r);
}

void rttr(int &x,LL l,LL r)
{
    int y=t[x].l;
    t[x].l=t[y].r;
    t[y].r=x;
    x=y;
    rebuild(y,l,r);
}

void ins(int &d,LL l,LL r,int x)
{
    LL mid=(l+r)/2;
    if (!d) {d=x;rank[d]=mid;t[d].k=rand()*rand();return;}
    if (cmp(x,d))
    {
        ins(t[d].l,l,mid,x);
        if (t[t[d].l].k<t[d].k) rttr(d,l,r);
    }
    else
    {
        ins(t[d].r,mid,r,x);
        if (t[t[d].r].k<t[d].k) rttl(d,l,r);
    }
}

void modify(int d,int l,int r,int x)
{
    if (l==r) {mn[d]=l;return;}
    int mid=(l+r)/2;
    if (x<=mid) modify(d*2,l,mid,x);
    else modify(d*2+1,mid+1,r,x);
    mn[d]=cmp(p[mn[d*2]],p[mn[d*2+1]])?mn[d*2]:mn[d*2+1];
}

int query(int d,int l,int r,int x,int y)
{
    if (x>y) return -1;
    if (l==x&&r==y) return mn[d];
    int mid=(l+r)/2;
    int u=query(d*2,l,mid,x,min(y,mid)),v=query(d*2+1,mid+1,r,max(x,mid+1),y);
    return v==-1||u>-1&&cmp(p[u],p[v])?u:v;
}

int main()
{
    n=read();m=read();len=read();ty=read();
    scanf("%s",str+1);
    reverse(str+1,str+len+1);s[0]=-1;
    for (int i=1;i<=len;i++) s[i]=str[i]-'a',ins(rt,0,inf,i);
    for (int i=1;i<=n;i++) p[i]=read(),modify(1,1,n,i);
    int ans=0;
    while (m--)
    {
        char ch[2];scanf("%s",ch);
        if (ch[0]=='I')
        {
            int c=read()^(ty*ans);s[++len]=c;
            ins(rt,0,inf,len);
        }
        else if (ch[0]=='C')
        {
            int x=read(),pos=read();
            p[x]=pos;
            modify(1,1,n,x);
        }
        else
        {
            int l=read(),r=read();
            printf("%d\n",ans=query(1,1,n,l,r));
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值