C Count New String(SAM建立n个子串)

题:https://ac.nowcoder.com/acm/contest/5669/C

题解:

分析:核心点1:当我们把原串第一次进行f函数后,第二次的f函数一定是对第一次经过f函数后的串进行取子串。

   核心点2:因为f函数的特性,这n个子串我们可以以10(字符集)*N的节点代价来建立字典树,考虑题解的俩个位置i,j,我们只需要记录插入 j 的last值,在插入时就插入到这个last值后面(tire树后面)(要插入j-i个)。

        这里只用插入j-i个的理由:按照核心点1的求法,我们可能要求插入(j-i)-1,插入(j-i)-2...长度允许的话,但大可不必,因为在统计不同子串的时候,最长的(插入(j-i)个)的个数已经全涵盖了当前(插入(j-i)-1,插入(j-i)-2...) 的所有子串。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int M=1000006;
int trans[M<<1][26],slink[M<<1],maxlen[M<<1];
int endpos[M<<1];
int last,now,root,len;

void init(){
    now=last=root=1;
}
void extend(int c){
    maxlen[++now]=maxlen[last]+1;
    int p=last,np=now;
    while(p&&!trans[p][c]){
        trans[p][c]=np;
        p=slink[p];
    }
    if(!p)
        slink[np]=root;
    else{
        int q=trans[p][c];
        if(maxlen[p]+1!=maxlen[q]){
            maxlen[++now]=maxlen[p]+1;
            int nq=now;
            memcpy(trans[nq],trans[q],sizeof(trans[q]));
            slink[nq]=slink[q];
            slink[q]=slink[np]=nq;
            while(p&&trans[p][c]==q){
                trans[p][c]=nq;
                p=slink[p];
            }
        }
        else
            slink[np]=q;
    }
    last=np;
    endpos[np]=1;
}
int pos[20],lastpos[M];
char s[M];
int main(){
    scanf("%s",s+1);
    init();
    int len=strlen(s+1);
    reverse(s+1,s+1+len);
    lastpos[0]=1;
    for(int i=1;i<=len;i++){
        last=lastpos[pos[s[i]-'a']];
        for(int j=pos[s[i]-'a']+1;j<=i;j++){
            extend(s[i]-'a');
            lastpos[j]=last;
        }
        for(int j=0;j<=s[i]-'a';j++)
            pos[j]=i;
    }
    ll ans=0;
    for(int i=root+1;i<=now;i++)
        ans+=maxlen[i]-maxlen[slink[i]];
    printf("%lld
",ans);
    return 0;
}
View Code

补充:sam的板子的last值是默认上一位的状态值,因为模板只用对一个字符串进行操作,而对多个串进行建sam时可以依据字符集的大小记录最后插入的last,每次插入时可直接在记录的last插入。

原文地址:https://www.cnblogs.com/starve/p/13354778.html