1. 程式人生 > >bzoj 1396: 識別子串

bzoj 1396: 識別子串

題目

\(SAM\)+線段樹

我竟然還會寫線段樹真是感人至深

考慮到我們只能計算出現一次的子串,所以我們直接先求一個子樹和,只對\(|endpos|=1\)的點操作就好了

我們在\(SAM\)插入的時候可以先存存好每一個字首的結尾位置在哪裡,之後我們對於每一個字首討論其出現次數為\(1\)的字尾

顯然在\(parent\)樹上深度越小的節點的\(len\)就越小,\(|endpos|\)也就越大,於是我們直接從每個字首的結尾節點往上跳,跳到深度最小的\(|endpos|=1\)的節點

這個可以通過樹上倍增做到,不過好像直接暴力看起來更有保障一些

\(x\)就是我們跳到的點,那麼非常顯然從\(i-len[fa[x]]+1\)

\(i\)這些位置的字尾出現次數超過了\(1\),而從\(1\)\(i-len[fa[x]]\)這些子串的出現次數為\(1\)

所以對於\([\ 1,i-len[fa[x]]\ ]\)這個區間裡的位置我們都可以標記出一個其能往後延伸的最近位置,顯然就是\(i\),而我們如果倒著迴圈的話,我們就可以保證\(i\)單減,所以我們甚至可以只用一個線段樹來通過區間覆蓋進行更新

而對於\([i-len[fa[x]]+1,i]\)這個區間的位置,我們發現其長度都是固定的,為\(len[fa[x]]+1\),但是我們好像不太能保證\(len[fa[x]]+1\)單調

沒有關係啊,我們離線下來排序就好了

程式碼

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 200005
#define re register
#define INF 999999999
#define min(a,b) ((a)<(b)?(a):(b))
int num,n,lst=1,cnt=1;
char S[maxn>>1];
int to[maxn>>1];
struct E{int v,nxt;} e[maxn];
struct C {int x,y,v;} a[maxn];
int fa[maxn],son[maxn][26],sz[maxn],len[maxn],head[maxn],f[maxn][19],deep[maxn],log_2[maxn];
inline int cmp(C A,C B) {return A.v>B.v;}
inline void add(int x,int y) {e[++num].v=y;e[num].nxt=head[x];head[x]=num;}
void dfs(int x) {for(re int i=head[x];i;i=e[i].nxt) deep[e[i].v]=deep[x]+1,dfs(e[i].v),sz[x]+=sz[e[i].v];}
inline void ins(int o,int c)
{
    int f=lst,p=++cnt; lst=p;
    len[p]=len[f]+1,sz[p]=1; to[o]=p;
    while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
    if(!f) {fa[p]=1;return;}
    int x=son[f][c];
    if(len[f]+1==len[x]) {fa[p]=x;return;}
    int y=++cnt;
    len[y]=len[f]+1,fa[y]=fa[x],fa[x]=fa[p]=y;
    for(re int i=0;i<26;i++) son[y][i]=son[x][i];
    while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
inline int find(int x)
{
    int k=log_2[deep[x]]+1;
    for(re int i=k;i>=0;--i) if(f[x][i]&&sz[f[x][i]]<=1) x=f[x][i];
    return x;
}
int l[maxn<<1],r[maxn<<1],tag[maxn<<1],d[maxn>>1],ans[maxn>>1],Ans[maxn>>1];
void build(int x,int y,int i) 
{
    l[i]=x,r[i]=y;tag[i]=INF; if(x==y) return;
    int mid=x+y>>1;
    build(x,mid,i<<1),build(mid+1,y,i<<1|1);
}
inline void pushdown(int i) {if(tag[i]==INF) return;tag[i<<1|1]=tag[i<<1]=tag[i];tag[i]=INF;}
void change(int x,int y,int val,int i)
{
    if(x<=l[i]&&y>=r[i]) {tag[i]=val;return;}
    pushdown(i);
    int mid=l[i]+r[i]>>1;
    if(y<=mid) change(x,y,val,i<<1);
        else if(x>mid) change(x,y,val,i<<1|1);
            else change(x,y,val,i<<1),change(x,y,val,i<<1|1);
}
int ask(int pos,int i)
{
    if(l[i]==pos&&pos==r[i]) return tag[i];
    pushdown(i);
    int mid=l[i]+r[i]>>1;
    if(pos<=mid) return ask(pos,i<<1); return ask(pos,i<<1|1);
}
int main()
{
    scanf("%s",S+1),n=strlen(S+1);
    for(re int i=1;i<=n;i++) ins(i,S[i]-'a');
    for(re int i=2;i<=cnt;i++) add(fa[i],i); deep[1]=1,dfs(1);
    for(re int i=2;i<=cnt;i++) log_2[i]=log_2[i>>1]+1;
    for(re int i=1;i<=cnt;i++) f[i][0]=fa[i];
    for(re int j=1;j<=log_2[cnt];j++)
        for(re int i=1;i<=cnt;i++) f[i][j]=f[f[i][j-1]][j-1];
    build(1,n,1);
    for(re int i=n;i;i--)
    {
        int x=find(to[i]);
        if(sz[x]>1) continue;
        int j=i-len[fa[x]];
        change(1,j,i,1);
        a[n-i+1].x=j+1,a[n-i+1].y=i,a[n-i+1].v=i-j+1;
    }
    for(re int i=1;i<=n;i++) d[i]=ask(i,1);
    std::sort(a+1,a+n+1,cmp);
    build(1,n,1);
    for(re int i=1;i<=n;i++) if(a[i].x<=a[i].y) change(a[i].x,a[i].y,a[i].v,1);
    for(re int i=1;i<=n;i++) ans[i]=ask(i,1),ans[i]=min(ans[i],d[i]-i+1);
    for(re int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}