http://poj.org/problem?id=1509
题意:就是求一个字符串的最小表示法开头字符的位置,如果有多个,求最小的位置。
思路:这道题用后缀自动机来做本身就是闲着,有点大材小用的感觉,这道题有别的更加精简的算法的,时间复杂度也为O(n),但是这里用SAM做能对SAM有更加清晰的认识,纯粹是为了练习SAM吧。
将原字符S串复制一下加到原字符串之后变成SS,则我们便是要求SS长度为|S|的字典序最小的字串,于是我们可以构造出SS的SAM,从root开始每次走最小编号的转移,走了|S|步以后到达的状态即为我们要求的子串(设为p),因为我们是要求开头的位置,并且是最小的一个,所以我们在每一个状态里保存一个变量l,表示这个状态在SS中出现的最左边的位置,则答案即为p->l-|s|+1,下面是代码。
#include <iostream> #include <string.h> #include <stdio.h> #include <algorithm> #define maxn 40010 using namespace std; char str[maxn>>1]; struct sam { sam *go[26],*par; int val; int l,po; }*root,*tail,que[maxn],*top[maxn]; int tot; void add(int c,int l,int po) { sam *p=tail,*np=&que[tot++]; np->po=po; np->val=l; while(p&&p->go[c]==NULL) p->go[c]=np,p=p->par; if(p==NULL) np->par=root; else { sam *q=p->go[c]; if(p->val+1==q->val) np->par=q; else { sam *nq=&que[tot++]; *nq=*q; nq->val=p->val+1; np->par=q->par=nq; while(p&&p->go[c]==q) p->go[c]=nq,p=p->par; } } tail=np; } int c[maxn],len; void init() { memset(que,0,sizeof(que)); tot=0; len=1; root=tail=&que[tot++]; } void solve(int n) { memset(c,0,sizeof(c)); int i; for(i=0;i<tot;i++) c[que[i].val]++; for(i=1;i<len;i++) c[i]+=c[i-1]; for(i=0;i<tot;i++) top[--c[que[i].val]]=&que[i]; for(sam *p=root;;p=p->go[str[p->val+1]-'a']) { p->l=p->po; //printf("%c",str[p->val+1]); if (p->val==len-1)break; } sam *p; for(i=tot-1;i>=0;i--) { p=top[i]; if(p->par) { sam *q=p->par; if(q->l==0||q->l>p->l) q->l=p->l; } } p=root; int tmp=n; while(tmp--) { int i; for(i=0;i<26;i++) { if(p->go[i]) { p=p->go[i]; break; } } } int ans=p->l-n+1; printf("%d\n",ans); } int main() { //freopen("dd.txt","r",stdin); int ncase; scanf("%d",&ncase); while(ncase--) { scanf("%s",str+1); init(); int l=strlen(str+1),i; for(i=1;i<=l;i++) { str[i+l]=str[i]; } for(i=1;i<=2*l;i++) add(str[i]-'a',len++,i); solve(l); } return 0; }
这里再放一个专门求字符串最小表示的算法的代码。简单有效。。。
#include <stdio.h> #include <stdlib.h> #include <string.h> #define MAXN 100000 int min(int a,int b) { return a<b?a:b; } int min_Str(char *str) { int i,j,k,len=strlen(str); i=0,j=1; memcpy(str+len,str,len); while(j<len&&i<len) { k=0; while(str[i+k]==str[j+k]) k++; if(k>=len) break; if(str[i+k]>str[j+k]) i=i+k+1; else j=j+k+1; if(i==j) j++; } return min(i,j); } char str[MAXN]; int main() { //freopen("dd.txt","r",stdin); int ncase; scanf("%d",&ncase); while(ncase--) { memset(str,'\0',sizeof(str)); scanf("%s",str); printf("%d\n",1+min_Str(str)); } return 0; }