SPOJ705 不同的子串
看来我的做法也没错嘛......
看别人的代码,写后缀数组的好像是利用SA[]和height[]什么的搞一搞,看了看表示没看懂
看了看后缀自动机众的代码,好像是用什么np->len-np->par->len更新答案,看了看表示理解不了
于是根据我对后缀自动机的理解强行YY了一发后缀自动机+DP的做法
我的想法是这样的:
后缀自动机上每条从始态到终态的路径与后缀一一对应,考虑到后缀自动机压缩了冗余状态,相同的子串在后缀自动机上走出的路径是会重合的
不难想到每条从始态到任意一点的路径应该也和每一个不同的子串一一对应,那么我们只要求出从始态到任意一点的路径数总和就好了
因为后缀自动机一定是DAG(废话,后缀和子串肯定是有限长的),只要跑一遍记忆化搜索就行了
一开始跪的原因是以为开点顺序一定是拓扑序,后来才想起来由于复制节点操作的存在开点顺序不一定是拓扑序......
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int maxn=50010; 6 void expand(int); 7 void dfs(int); 8 int root,last,cnt=0,val[maxn<<1]={0},par[maxn<<1]={0},go[maxn<<1][26]={{0}}; 9 bool vis[maxn<<1]={false}; 10 char s[maxn]; 11 int n,f[maxn<<1]={0}; 12 int main(){ 13 freopen("subst1.in","r",stdin); 14 freopen("subst1.out","w",stdout); 15 root=last=++cnt; 16 scanf("%s",s); 17 n=strlen(s); 18 for(int i=0;i<n;i++)expand(s[i]-'A'); 19 dfs(root); 20 printf("%d",f[root]-1); 21 return 0; 22 } 23 void expand(int c){ 24 int p=last,np=++cnt; 25 val[np]=val[p]+1; 26 while(p&&!go[p][c]){ 27 go[p][c]=np; 28 p=par[p]; 29 } 30 if(!p)par[np]=root; 31 else{ 32 int q=go[p][c]; 33 if(val[q]==val[p]+1)par[np]=q; 34 else{ 35 int nq=++cnt; 36 memcpy(go[nq],go[q],sizeof(go[q])); 37 val[nq]=val[p]+1; 38 par[nq]=par[q]; 39 par[q]=nq; 40 par[np]=nq; 41 while(p&&go[p][c]==q){ 42 go[p][c]=nq; 43 p=par[p]; 44 } 45 } 46 } 47 last=np; 48 } 49 void dfs(int x){ 50 vis[x]=true; 51 f[x]=1; 52 for(int c=0;c<26;c++)if(go[x][c]){ 53 if(!vis[go[x][c]])dfs(go[x][c]); 54 f[x]+=f[go[x][c]]; 55 } 56 }
然而生成魔咒那题想不到合适的做法在线维护(也许需要用到DAG的支配树?然而我不会......),还是乖乖抄了用np->len-np->par->len更新答案的做法才过的......
233333333