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

Problem

  给定一个长为L(≤100000)的字符串S,对于S中的每一个位置k,求包含它且在S中仅出现一次的子串的最短长度。(即i≤k≤j,且S(i..j)在S中仅出现一次)

Solution

  • 这道题的AC经历让我懂得了一个道理——做字符串的题,只要不是哈希,就往SAM的方面想。。。

  • 前置技能:后缀自动机(SAM)
  • 首先,我们知道,SAM中的每一个状态表示一坨长度为一段连续区间的字符串,不妨设长度为[mi,len]。
  • 如果求出每个状态的mi,那就很好做了。

  • 假设我们求出了mi,考虑如何计算答案。
  • 对于状态i,若sz[i](i在S中的出现次数)==1,则表明此状态所代表的所有字符串的出现次数也均为1,那么这些字符串就是可能的答案。计算sz的话,你每次新建的点的sz初始为1,克隆的点的sz初始为0;最后,你将所有状态按len排序,然后倒着扫一遍,令sz[fa[i]]+=sz[i]。(因为fa[i]实质上为i的后缀,所以len[fa[i]]<len[i])
  • 对于这样的状态i,我们记录一个right[i](以下简写为r)表示它在S中的右端点。显然,S(r-mi+1..r)是i所代表的所有字符串中最短的一个。我们显然要拿它搞事。

  • 考虑维护一棵线段树,表示每个位置的答案。每个位置一开始初始化为n。
  • 对于上述的S(r-mi+1..r),显然它在S中仅出现一次,所以令线段树中的这段区间与mi取个min。
  • 但是这样可能会错。就是说,假设S(i+1..i+3)的答案为3,S[i]的答案却为100;那么,S(i+1..i+3)既然只出现过一次,S(i..i+3)肯定也只出现过一次,于是S[i]的答案要与3+1=4取个min。一般点说,我们倒着扫一遍,令ans[i]=min(ans[i],ans[i+1]+1)。
  • 至此,我们优雅地解决了求出mi以后计算答案的难点。

  • 现在回到求解mi的问题上。(可以直接跳到两条分割线后)
  • 根据SAM的性质,从根节点沿着字符边走到任一节点,经过的边所形成的字符串都是原串的一个子串。
  • 所以说,mi[i]实际上就等于i到根的最短距离。
  • 处理这个,你可以到最后拓扑排序递推一波(因为字符边构成的图会形成一个DAG);当然,你也可以像我一样,边构建自动机边处理。

  • 下面介绍一下我的处理方法。
  • 设当前位置的字符为c(即S[i]=c)。我们在新建一个节点now(当前状态,即代表S(1..i)的状态)以后,会让点u从last(上一个状态,即代表S(1..i-1)的状态)出发,若u没有c的字符边出边,则让这些u向now连字符边,所以mi[now]=min{mi[u]+1};而当我们找到某个u有c的字符边时,设它连向v,若len[u]+1≠len[v](实质上只有=和<两种情况,所以该式中的“≠”可改为“<”),我们就要克隆一个点v。
  • 设克隆出来的新点为np。原本的状态v包含长度为mi[v]~len[v]的字符串,而现在,np帮它分担了长度为长度≤len[u]+1的字符串,即长度为mi[v]~len[u]+1的;于是,原本的v所包含的字符串的长度区间就缩减成len[u]+2~len[v]。形式上,mi[np]=mi[v],而mi[v]更改为len[u]+2。

  • 我昨天傻逼了其实mi有一种更为简洁的求法。构出自动机后,mi[i]=len[fa[i]]+1。
  • 因为fa[i]是i的最长后缀;而SAM的所有状态包含了原串的所有子串。所以说,既然有代表S(r-mi+1..r)的状态(mi≥1),也肯定会有代表S(r-mi+2..r)的状态,而后者即为fa[i]。

  • 时间复杂度: O ( n l o g 2 n )

Code

#include <cstdio>
#include <cstring>
#include <iostream>
#define A v<<1
#define B A|1
#define MIN(x,y) x=min(x,y)
#define min(x,y) ((x)<(y)?(x):(y))
#define max(x,y) ((x)>(y)?(x):(y))
#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 N=2e5+9,K=26;
char s[N];
int n,i,fa[N],siz[N],len[N],c[N],a[N],t[N<<2],tag[N<<2],px,py,pv,ans[N];
typedef long long ll;

void maketree(int v,int x,int y)
{
    t[v]=n;
    if(x==y) return;
    int mid=x+y>>1;
    maketree(A,x,mid); maketree(B,mid+1,y);
}
inline void mark(int v,int pv)
{
    if(!v) return;
    if(t[v]>pv) t[v]=tag[v]=pv;
}
inline void push(int v)
{
    if(!tag[v]) return;
    mark(A,tag[v]); mark(B,tag[v]);
    tag[v]=0;
}
void modify(int v,int x,int y)
{
    if(py< x||y< px) return;
    push(v);
    if(px<=x&&y<=py){mark(v,pv); return;}
    int mid=x+y>>1;
    modify(A,x,mid); modify(B,mid+1,y);
}
void query(int v,int x,int y)
{
    push(v);
    if(x==y){ans[x]=t[v]; return;}
    int mid=x+y>>1;
    query(A,x,mid); query(B,mid+1,y); 
}
//segment_tree 

struct SAM
{
    ll ans,now,u,v,np,root,cnt,last,i,ch[N][K],right[N];
    ll newnode(ll x)
    {
        len[++cnt]=x,fa[cnt]=0; int i;
        fo(i,0,K-1) ch[cnt][i]=0;
        return cnt;
    }
    void init()
    {
        cnt=0;
        newnode(0);
        last=root=1;
    }
    void extend(ll c,ll i)
    {
        now=newnode(len[last]+1),u=last; 
        for (;u && !ch[u][c];u=fa[u]) ch[u][c]=now;
        if (!u) fa[now]=1;else
        {
            v=ch[u][c];
            if (len[u]+1==len[v]) fa[now]=v;else 
            {
                np=newnode(len[u]+1),memcpy(ch[np],ch[v],sizeof ch[np]);
                right[np]=right[v]; 
                for (;u && ch[u][c]==v;u=fa[u]) ch[u][c]=np;
                fa[np]=fa[v],fa[v]=fa[now]=np; 
            }
        }
        siz[now]=1; right[now]=i;
        last=now;
    }
    void work()
    {
        ans=0;
        fo(i,1,cnt) c[len[i]]++;
        fo(i,1,cnt) c[i]+=c[i-1];
        fo(i,1,cnt) a[c[len[i]]--]=i;
        fd(i,cnt,1) siz[fa[a[i]]]+=siz[a[i]];
        maketree(1,1,n);
        fo(i,2,cnt)
            if (siz[i]==1)
            {
                pv=len[fa[i]]+1; py=right[i]; px=py-pv+1;
                modify(1,1,n);
            }
    }
} run;
int main()
{
    scanf("%s",s+1),n=strlen(s+1);
    run.init();
    fo(i,1,n) run.extend(s[i]-'a',i);
    run.work();
    query(1,1,n);
    fd(i,n-1,1) MIN(ans[i],ans[i+1]+1);
    fo(i,1,n) printf("%d\n",ans[i]); 
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/81037784