Ví dụ: S = "abcbc"
Xâu con "bc" xuất hiện kết thúc tại vị trí {3, 5}
Xâu con "abc" xuất hiện kết thúc tại vị trí {3}
→ "bc" và "abc" KHÁC lớp (khác endpos)
structState{intlen;// Độ dài dài nhất trong trạng tháiintlink;// Suffix linkmap<char,int>next;// Các cạnh chuyển (hoặc int next[26] cho chữ cái thường)};
Khi len[p] + 1 < len[q]:
- Tạo clone với len = len[p] + 1
- Sao chép tất cả cạnh và suffix link từ q
- Cập nhật: link[clone] = link[q], link[q] = link[cur] = clone
- Sửa các cạnh next[p][c] trỏ đến q thành trỏ đến clone (dọc suffix link)
#include<bits/stdc++.h>usingnamespacestd;constintMAXLEN=200005;// Kích thước xâu tối đastructSuffixAutomaton{structState{intlen;// Độ dài dài nhất trong trạng tháiintlink;// Suffix linkintnext[26];// Cạnh chuyển cho 26 chữ cái thườngState():len(0),link(-1){memset(next,-1,sizeof(next));}};Statest[MAXLEN*2];// Tối đa 2N - 1 trạng tháiintsz;// Số trạng thái hiện tạiintlast;// Trạng thái cuối cùng (đại diện cho toàn bộ xâu hiện tại)SuffixAutomaton(){st[0].len=0;st[0].link=-1;sz=1;last=0;}voidextend(charch){intc=ch-'a';intcur=sz++;st[cur].len=st[last].len+1;intp=last;// Bước 1: Duyệt suffix link, thêm cạnh trỏ đến curwhile(p!=-1&&st[p].next[c]==-1){st[p].next[c]=cur;p=st[p].link;}if(p==-1){// Không tìm thấy trạng thái nào có cạnh cst[cur].link=0;}else{intq=st[p].next[c];if(st[p].len+1==st[q].len){// Trường hợp tốt: len[q] = len[p] + 1st[cur].link=q;}else{// Cần clone trạng thái qintclone=sz++;st[clone].len=st[p].len+1;st[clone].link=st[q].link;memcpy(st[clone].next,st[q].next,sizeof(st[q].next));// Sửa các cạnh trỏ đến q → trỏ đến clonewhile(p!=-1&&st[p].next[c]==q){st[p].next[c]=clone;p=st[p].link;}st[q].link=st[cur].link=clone;}}last=cur;}voidbuild(conststring&s){for(charch:s){extend(ch);}}};intmain(){ios_base::sync_with_stdio(false);cin.tie(NULL);strings;cin>>s;SuffixAutomatonsam;sam.build(s);// In thông tin SAMfor(inti=0;i<sam.sz;i++){cout<<"State "<<i<<": len="<<sam.st[i].len<<", link="<<sam.st[i].link<<"\n";}return0;}
Tạo state 1: len=1
p = 0 (init), c = 'a'
state 0 chưa có cạnh 'a' → next[0]['a'] = 1
p = link[0] = -1 → dừng
p == -1 → link[1] = 0
Kết quả:
State 0: len=0, link=-1, next={a:1}
State 1: len=1, link=0
last = 1
Tạo state 2: len=2
p = 1, c = 'b'
state 1 chưa có cạnh 'b' → next[1]['b'] = 2
p = link[1] = 0
state 0 chưa có cạnh 'b' → next[0]['b'] = 2
p = link[0] = -1 → dừng
p == -1 → link[2] = 0
Kết quả:
State 0: len=0, next={a:1, b:2}
State 1: len=1, link=0, next={b:2}
State 2: len=2, link=0
last = 2
Tạo state 3: len=3
p = 2, c = 'c'
state 2 chưa có cạnh 'c' → next[2]['c'] = 3
p = link[2] = 0
state 0 chưa có cạnh 'c' → next[0]['c'] = 3
p = link[0] = -1 → dừng
p == -1 → link[3] = 0
Kết quả:
State 0: len=0, next={a:1, b:2, c:3}
State 1: len=1, link=0, next={b:2}
State 2: len=2, link=0, next={c:3}
State 3: len=3, link=0
last = 3
#include<bits/stdc++.h>usingnamespacestd;constintMAXLEN=200005;structSuffixAutomaton{structState{intlen,link;intnext[26];State():len(0),link(-1){memset(next,-1,sizeof(next));}};Statest[MAXLEN*2];intsz,last;SuffixAutomaton(){st[0].len=0;st[0].link=-1;sz=1;last=0;}voidextend(charch){intc=ch-'a';intcur=sz++;st[cur].len=st[last].len+1;intp=last;while(p!=-1&&st[p].next[c]==-1){st[p].next[c]=cur;p=st[p].link;}if(p==-1){st[cur].link=0;}else{intq=st[p].next[c];if(st[p].len+1==st[q].len){st[cur].link=q;}else{intclone=sz++;st[clone].len=st[p].len+1;st[clone].link=st[q].link;memcpy(st[clone].next,st[q].next,sizeof(st[q].next));while(p!=-1&&st[p].next[c]==q){st[p].next[c]=clone;p=st[p].link;}st[q].link=st[cur].link=clone;}}last=cur;}voidbuild(conststring&s){for(charch:s)extend(ch);}// Tìm xâu con chung dài nhất với xâu tstringlongestCommonSubstring(conststring&t){intv=0,l=0,best=0,bestpos=0;for(inti=0;i<(int)t.size();i++){intc=t[i]-'a';if(st[v].next[c]!=-1){v=st[v].next[c];l++;}else{while(v!=-1&&st[v].next[c]==-1){v=st[v].link;}if(v==-1){v=0;l=0;}else{l=st[v].len+1;v=st[v].next[c];}}if(l>best){best=l;bestpos=i;}}returnt.substr(bestpos-best+1,best);}};intmain(){ios_base::sync_with_stdio(false);cin.tie(NULL);strings1,s2;cin>>s1>>s2;SuffixAutomatonsam;sam.build(s1);stringlcs=sam.longestCommonSubstring(s2);cout<<lcs.size()<<"\n";if(!lcs.empty())cout<<lcs<<"\n";return0;}
S₁ = "abcbc"
S₂ = "cbabd"
Duyệt S₂ trên SAM của S₁:
'c' → state 3 (len=1) → l=1
'b' → state 4 (len=2) → l=2 (xâu "cb")
'a' → không có cạnh từ state 4
→ link[4]=5, next[5]['a']? Không
→ link[5]=0, next[0]['a']=1 → state 1, l=1
'b' → state 2 (len=2) → l=2 (xâu "ab")
'd' → không có cạnh → l=0
LCS = "cb" hoặc "ab", độ dài = 2
#include<bits/stdc++.h>usingnamespacestd;constintMAXLEN=200005;structSuffixAutomaton{structState{intlen,link;intnext[26];longlongcnt;// Số lần xuất hiệnState():len(0),link(-1),cnt(0){memset(next,-1,sizeof(next));}};Statest[MAXLEN*2];intsz,last;SuffixAutomaton(){st[0].len=0;st[0].link=-1;sz=1;last=0;}voidextend(charch){intc=ch-'a';intcur=sz++;st[cur].len=st[last].len+1;st[cur].cnt=1;// Mỗi trạng thái mới đại diện cho 1 vị trí kết thúcintp=last;while(p!=-1&&st[p].next[c]==-1){st[p].next[c]=cur;p=st[p].link;}if(p==-1){st[cur].link=0;}else{intq=st[p].next[c];if(st[p].len+1==st[q].len){st[cur].link=q;}else{intclone=sz++;st[clone].len=st[p].len+1;st[clone].link=st[q].link;st[clone].cnt=0;// Clone không thêm vị trí kết thúc mớimemcpy(st[clone].next,st[q].next,sizeof(st[q].next));while(p!=-1&&st[p].next[c]==q){st[p].next[c]=clone;p=st[p].link;}st[q].link=st[cur].link=clone;}}last=cur;}voidbuild(conststring&s){for(charch:s)extend(ch);}// Tính số lần xuất hiện cho mỗi trạng tháivoidcomputeOccurrences(){// Sắp xếp theo len giảm dần bằng counting sortvector<int>cnt_by_len(MAXLEN*2,0);for(inti=0;i<sz;i++)cnt_by_len[st[i].len]++;for(inti=1;i<MAXLEN*2;i++)cnt_by_len[i]+=cnt_by_len[i-1];vector<int>order(sz);for(inti=sz-1;i>=0;i--){order[--cnt_by_len[st[i].len]]=i;}// Duyệt từ trạng thái có len lớn nhấtfor(inti=sz-1;i>=1;i--){intv=order[i];if(st[v].link!=-1){st[st[v].link].cnt+=st[v].cnt;}}}// Tìm số lần xuất hiện của xâu con tlonglongcountOccurrences(conststring&t){intv=0;for(charch:t){intc=ch-'a';if(st[v].next[c]==-1)return0;v=st[v].next[c];}returnst[v].cnt;}};intmain(){ios_base::sync_with_stdio(false);cin.tie(NULL);strings;cin>>s;SuffixAutomatonsam;sam.build(s);sam.computeOccurrences();intq;cin>>q;while(q--){stringt;cin>>t;cout<<sam.countOccurrences(t)<<"\n";}return0;}
#include<bits/stdc++.h>usingnamespacestd;constintMAXLEN=200005;structSuffixAutomaton{structState{intlen,link;intnext[26];longlongcnt;// Số đường đi từ trạng thái nàyState():len(0),link(-1),cnt(-1){memset(next,-1,sizeof(next));}};Statest[MAXLEN*2];intsz,last;SuffixAutomaton(){st[0].len=0;st[0].link=-1;sz=1;last=0;}voidextend(charch){intc=ch-'a';intcur=sz++;st[cur].len=st[last].len+1;intp=last;while(p!=-1&&st[p].next[c]==-1){st[p].next[c]=cur;p=st[p].link;}if(p==-1){st[cur].link=0;}else{intq=st[p].next[c];if(st[p].len+1==st[q].len){st[cur].link=q;}else{intclone=sz++;st[clone].len=st[p].len+1;st[clone].link=st[q].link;memcpy(st[clone].next,st[q].next,sizeof(st[q].next));while(p!=-1&&st[p].next[c]==q){st[p].next[c]=clone;p=st[p].link;}st[q].link=st[cur].link=clone;}}last=cur;}voidbuild(conststring&s){for(charch:s)extend(ch);}// Đếm số đường đi (số xâu con) từ trạng thái vlonglongcountPaths(intv){if(st[v].cnt!=-1)returnst[v].cnt;st[v].cnt=1;// Xâu rỗng (trạng thái hiện tại)for(intc=0;c<26;c++){if(st[v].next[c]!=-1){st[v].cnt+=countPaths(st[v].next[c]);}}returnst[v].cnt;}// Tìm xâu con thứ K (1-indexed)stringkthSubstring(longlongk){countPaths(0);if(k>st[0].cnt-1)return"";// Không đủ xâu constringresult;intv=0;while(k>0){for(intc=0;c<26;c++){if(st[v].next[c]==-1)continue;intu=st[v].next[c];if(k<=st[u].cnt){result+=(char)('a'+c);v=u;break;}k-=st[u].cnt;}}returnresult;}};intmain(){ios_base::sync_with_stdio(false);cin.tie(NULL);strings;longlongk;cin>>s>>k;SuffixAutomatonsam;sam.build(s);stringans=sam.kthSubstring(k);if(ans.empty()){cout<<"No such substring.\n";}else{cout<<ans<<"\n";}return0;}
importsyssys.setrecursionlimit(300000)classSuffixAutomaton:def__init__(self,max_states=400010):self.next=[[-1]*26for_inrange(max_states)]self.link=[-1]*max_statesself.length=[0]*max_statesself.cnt=[-1]*max_statesself.sz=1self.last=0defextend(self,ch):c=ord(ch)-ord('a')cur=self.sz;self.sz+=1self.length[cur]=self.length[self.last]+1p=self.lastwhilep!=-1andself.next[p][c]==-1:self.next[p][c]=curp=self.link[p]ifp==-1:self.link[cur]=0else:q=self.next[p][c]ifself.length[p]+1==self.length[q]:self.link[cur]=qelse:clone=self.sz;self.sz+=1self.length[clone]=self.length[p]+1self.link[clone]=self.link[q]self.next[clone]=self.next[q][:]whilep!=-1andself.next[p][c]==q:self.next[p][c]=clonep=self.link[p]self.link[q]=cloneself.link[cur]=cloneself.last=curdefbuild(self,s):forchins:self.extend(ch)defcount_paths(self,v):ifself.cnt[v]!=-1:returnself.cnt[v]self.cnt[v]=1forcinrange(26):ifself.next[v][c]!=-1:self.cnt[v]+=self.count_paths(self.next[v][c])returnself.cnt[v]defkth_substring(self,k):self.count_paths(0)ifk>self.cnt[0]-1:return""result=[]v=0whilek>0:forcinrange(26):ifself.next[v][c]==-1:continueu=self.next[v][c]ifk<=self.cnt[u]:result.append(chr(ord('a')+c))v=ubreakk-=self.cnt[u]return''.join(result)s=input().strip()k=int(input())sam=SuffixAutomaton()sam.build(s)ans=sam.kth_substring(k)ifnotans:print("No such substring.")else:print(ans)
Suffix Automaton = DFA tối thiểu nhận tất cả hậu tố
├── Trạng thái = lớp tương đương endpos
├── len[v] = độ dài dài nhất trong trạng thái v
├── link[v] = suffix link → hậu tố dài nhất khác lớp
├── next[v][c] = cạnh chuyển
│
├── Xây dựng: O(N) online
├── Bộ nhớ: O(N) trạng thái, O(N) cạnh
│
└── Ứng dụng:
├── Đếm xâu con khác nhau: Σ(len[v] - len[link[v]])
├── LCS hai xâu: match trên SAM
├── Số lần xuất hiện: DP trên suffix link tree
└── Xâu con thứ K: DP đếm đường đi + duyệt