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