4372. 【GDOI2016模拟】识别子串(SAM+线段树)

题目

  • 有一个字符串 S S ,T=S[i..j] k k 的的识别子串当且仅当
  • 1.i<=k<=j
  • 2. T T S中仅出现一次
    求每个位置最短的识别子串

想法

  • 想到用SAM搞定每个子串的出现次数,然后用线段树区间修改
  • SAM上的状态i表示的是以某个位置 x x 为右端点,以xlen[i]+1....xmi[i]+1的位置为左端的一堆子串
  • siz[i] s i z [ i ] 则表示i状态的出现次数
  • mi[i] m i [ i ] 就表示root到状态i的最短距离,可以建完SAM后bfs一遍
  • 也可以在建SAM时,当一个状态 u u 向状态now连一条字符边时, mi[now]=min(mi[now],mi[u]+1) m i [ n o w ] = m i n ( m i [ n o w ] , m i [ u ] + 1 )
  • len[u]+1<len[v] l e n [ u ] + 1 < l e n [ v ] 时, mi[np]=mi[v],mi[v]=mi[np]+1 m i [ n p ] = m i [ v ] , m i [ v ] = m i [ n p ] + 1
  • mi m i 就搞定了
  • 在建SAM时, siz[now]=1siz[np]=0 s i z [ n o w ] = 1 , s i z [ n p ] = 0
  • 然后 siz[fa[i]]+=siz[i] s i z [ f a [ i ] ] + = s i z [ i ] 因为i状态是fa[i]状态的一个子集
    求出所有的 siz[i]==1 s i z [ i ] == 1 的状态,然后在区间 fro[i]mi[i]+1...fro[i] f r o [ i ] − m i [ i ] + 1... f r o [ i ] mi[i] m i [ i ] 取min, fro[i] f r o [ i ] 是i状态的右端点
  • 由于一个子串 T T 包含一个识别子串T时, T T 也是一个子串
  • 所以最后要ans[i]=min(ans[i],ans[i+1]+1)(因为有些位置可能没被覆盖)
#include <cstdio>
#include <cstring>
#include <iostream>
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
using namespace std;
const int maxN=2e5+10,maxK=26;
char s[maxN];
int n,i,tr[maxN*4],ans[maxN],ans1,l,r,zl,y;
void find(int x,int head,int tail){
    if(l<=head && tail<=r){
        if (zl==0) tr[x]=min(tr[x],y);
        if (zl==1) ans1=min(ans1,tr[x]);
        return;
    }
    tr[x*2]=min(tr[x*2],tr[x]);
    tr[x*2+1]=min(tr[x*2+1],tr[x]);
    int mid=(head+tail)/2;
    if (l<=mid) find(x*2,head,mid);
    if (mid<r) find(x*2+1,mid+1,tail);
}
struct sam{
    int len[maxN],mi[maxN],siz[maxN],a[maxN],cnt[maxN],
    ch[maxN][maxK],p,now,last,u,v,pre[maxN],np,fro[maxN],x,i;
    int newnode(int x){
        len[++p]=x,pre[p]=siz[p]=mi[p]=0;
        fo(i,0,25) 
            ch[p][i]=0;
        return p;
    } 
    void init(){
        p=0;
        newnode(0);
        last=p; 
    }
    void add(int c,int i){
        now=newnode(len[last]+1),mi[now]=len[now],fro[now]=i;
        for(u=last;u && !ch[u][c];u=pre[u]) //细节
            ch[u][c]=now,mi[now]=min(mi[u]+1,mi[now]);
        if (!u) pre[now]=1;else{
            v=ch[u][c];
            if (len[u]+1==len[v]) pre[now]=v;else{
                np=newnode(len[u]+1),memcpy(ch[np],ch[v],sizeof(ch[np]));//细节
                pre[np]=pre[v],pre[v]=pre[now]=np,mi[np]=mi[v],mi[v]=len[u]+2,
                fro[np]=fro[v];
                for(;u && ch[u][c]==v;u=pre[u]) ch[u][c]=np;
            }
        }
        last=now,siz[now]=1;
    }
    void work(){
        fo(i,1,p) cnt[len[i]]++;
        fo(i,1,p) cnt[i]+=cnt[i-1];
        fo(i,1,p) 
            a[cnt[len[i]]--]=i;
        fd(i,p,1) 
            siz[pre[a[i]]]+=siz[a[i]];
        fo(i,1,p){
            if (siz[i]==1){
                r=fro[i],y=mi[i],l=r-y+1,zl=0,
                find(1,1,n);
            }
        }
    }
}run;
int main(){
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%s",s+1),n=strlen(s+1);
    run.init();
    fo(i,1,n)
        run.add(s[i]-'a',i);
    fo(i,1,4*n) tr[i]=n;    
    run.work();
    fo(i,1,n)
        l=r=i,zl=1,ans1=n,find(1,1,n),ans[i]=ans1;
    fd(i,n-1,1)
        ans[i]=min(ans[i],ans[i+1]+1);
    fo(i,1,n)
        printf("%d\n",ans[i]);
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值