HDU 4270 SAM 后缀自动机

题目:给出一个串,有3种操作,
操作1:在原串后面,添加一个串
操作2:查询长度为len的子串或者长度小于等于len的后缀中字典序最小的

操作3:删除最后的len个字符


关于删除

首先明确一点:  新建的点只与模板点有关系

由新加入的点7产生的点8是根据点3为模板进行复制的(复制所有信息,包括位置)。那么点8是有点3控制的。

可以从两个图看出,删除点7,不删除点8也是可以达到第一个图的效果。

只是图不是最简的SAM。即a-a-b的父亲变为了a-b。但是无关大雅。

从这个可以看出新建立的辅助点是根据某点为模板,并且为这个模板分担了一些线路的点。

模板点控制这这些辅助点。当且仅当这个模板点被删除后,以这个模板点为模板扩展出去的辅助点才会也才能删除。


声明一个bool is_del[]

结构体里加上一个 bool *d 指针

字符串里的每一个结点对应一个域,即d=is_del[x],那么其复制出来的点也共享这个is_del[x]

当这个字符被删除后,也就是说这个域被删除后,所有的点的*d==0  ,全部删除了


删除从最后一个字符(域)开始删除,删除len个字符(域)即可

删除完之后last需要更新到删除后的第一个点


关于询问

node记录下该点的位置,如果这个状态是后缀,则ans=seq_cnt-i+1

否则为ans=p->pos-i+1

#include <iostream>
#include <cstring>
#include <cstdio>
#include <string>
using namespace std;

const int maxn=200005;
struct suffixautomaton
{
    struct node
    {
        int len,suf;//到这个状态允许的最大长长度,即max(s)
        node *f,*ch[26];  //如果suf==v_idx,则意味这个状态是后缀子串
        bool *d;  //
        int pos;  //字符原来的位置
        node(){}
        node(int l)
        {
            len=l;
            f=NULL;
            memset(ch,0,sizeof(ch));
        }
    };
    node *root,*last;
    node pool[maxn*2];  //储蓄结点用的
    node *seq[maxn];  //用于储存输入进来的字符串,,其实就是储存pool数组中主心轴的点。
    bool is_del[maxn*2];  //实际上is_del[i] 是代表第i个字符被删除没。node里的 *d是某一个is_del的地址,同一个域里的点共享一个is_del,同在一个域里面的点共享一个is_del[x].
    int del_cnt,seq_cnt;  //is_del数组的大小,seq数组的大小
    int cnt;             //结点的数量
    int v_idx;        //此处的v_idx方便设置suf,避免出错
    void init()
    {
        v_idx=0;
        memset(is_del,0,sizeof(is_del));
        del_cnt=seq_cnt=0;
        root=last=pool;
        seq[0]=root;    //此处必须先设置第0位root,避免数组越界
        memset(root,0,sizeof(node));
        cnt=1;
    }
    node * new_node(int l=0)
    {
        node *x=pool+cnt++;
        memset(x,0,sizeof(node));
        if(l!=0) x->len=l;
        return x;
    }
    void add(char ch)
    {
        int c=ch-'a';
        node *p=last,*np=new_node(last->len+1);
        np->pos=np->len;
        np->d=is_del+del_cnt++;
        *np->d=0;
        seq[seq_cnt=np->len]=np;      //以上四行初始化

        last=np;
        for(;NULL!=p&&(NULL==p->ch[c]||*p->ch[c]->d);p=p->f)  //如果这个点没有被删除且无下一个点ch
            p->ch[c]=np;
        if(NULL==p)
            np->f=root;
        else
        {
            if(p->ch[c]->len==p->len+1)
                np->f=p->ch[c];
            else
            { //新建立的点是以p->ch[c]为模板复制的点,共享一个is_del[],当且仅当模板点被删除后新建立的点才会被删除。
                node *q=p->ch[c],*nq=new_node();
                *nq=*q;
                nq->len=p->len+1;
                q->f=np->f=nq;
                for(;NULL!=p&&p->ch[c]==q;p=p->f)
                    p->ch[c]=nq;
            }
        }
    }
    void get_suf()//如果suf==v_idx,则意味这个状态是后缀子串
    {
        v_idx++;
        node *p=last;
        while(p!=NULL)
            p->suf=v_idx,p=p->f;
        root->suf=0;
    }
    void del(int len)  //从最后一个域开始删除
    {
        while(len--)
            *seq[seq_cnt--]->d=1;
        last=seq[seq_cnt];  //last需要改变
    }
    void query(int l)
    {
        get_suf();
        node *p=root;
        for(int i=1;i<=l;i++)
        {
            for(int j=0;j<26;j++)
                if(!(NULL==p->ch[j]||*p->ch[j]->d))
                {
                    p=p->ch[j];
                    if(i==l)
                        cout<<p->pos-i+1<<endl;
                    else if(p->suf==v_idx)
                    {
                        cout<<seq_cnt-i+1<<endl;
                        return ;
                    }
                    break;
                }
        }
    }
};
suffixautomaton sam;
char str[maxn];
int main()
{
    while(scanf("%s",str)!=EOF)
    {
        sam.init();
        for(int i=0;str[i];i++)
            sam.add(str[i]);

        int q,cas,len;
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d",&cas);
            if(cas==1)
            {
                scanf("%s",str);
                for(int i=0;str[i];i++)
                    sam.add(str[i]);
            }
            else if(cas==2)
            {
                scanf("%d",&len);
                sam.query(len);
            }
            else
            {
                scanf("%d",&len);
                sam.del(len);
            }
        }
    }
    return 0;
}





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值