構造
\(a\)是字符集,\(|s|=n\),\(nxt[i][j]\)表示\(i\)以后的第一個字符\(j\)的位置,\(0\)為根節點,整個圖是一個\(DAG\)
for(LL i=n;i>=1;--i){
for(LL j=1;j<=a;++j) nxt[i-1][j]=nxt[i][j];
nxt[i-1][s[i]]=i;
}
擴展構建
當字符集較大時,可套用可持久化,在葉子節點放一個\(id\),表示出邊
相關例題:
字符串\(K\)小子序列,可持久化序列自動機,維護節點大小
一步一步(從首到尾)走,有序確定code
經典例題
判斷是否是原字符串的子序列
構造出了\(nxt\)后,從根跑一遍就好了
求子序列個數
從根跑,記憶化搜索,\(f[x]\)為點\(x\)為首的子序列個數,\(f[y]=(\sum\limits_{x\in y'son}f[x])+1\)
求兩串的公共子序列個數
兩串都構造一下,之間跑就好了
LL Dfs(LL x,LL y){
if(f[x][y]) return f[x][y];
for(LL i=1;i<=a;++i)
if(nxt1[x][i]&&nxt2[y][i])
f[x][y]+=Dfs(nxt1[x][i],nxt2[y][i]);
return ++f[x][y];
}
求字符串的回文子序列個數
原串與反串都建一遍
\[\begin{aligned}\longrightarrow 1~~2~~3~~4~~5~~6~~7~~8~~9~~10&\\ 10~~9~~8~~7~~6~~5~~4~~3~~2~~1&\longleftarrow\\ \end{aligned}\]
就相當於從左右端點這樣跑
求的時候顯然\(x+y≤n+1\)這個序列才是合法的
\(x+y=n+1\)時就是會合了一樣,在之后的遍歷過程會\(++f[x][y]\),所以暫時不統計
但是其他情況我們都是匹配的兩個字符,也就是只會統計\(abba\),而統計不了\(aba\),所以在過程中\(++f[x][y]\)
LL Dfs(LL x,LL y){
if(f[x][y]) return f[x][y];
for(LL i=1;i<=a;++i)
if(nxt1[x][i]&&nxt2[y][i]){
if(nxt1[x][i]+nxt2[y][i]>n+1) continue;
if(nxt1[x][i]+nxt2[y][i]<n+1) f[x][y]++;
f[x][y]=(f[x][y]+Dfs(nxt1[x][i],nxt2[y][i]))%mod;
}
return ++f[x][y];
}
求一個\(A,B\)的最長公共子序列\(S\),使得\(C\)是\(S\)的子序列
還是同樣的\(Dfs(x,y,z)\),表示一匹配到\(C\)的\(z\)位
改變一下\(C\)的構建方法
for(LL i=1;i<=a;++i) nxt[n][i]=n;
for(LL i=0;i<n;++i){
for(LL j=1;j<=a;++j) nxt[i][j]=i;
nxt[i][c[i+1]]=i+1;
}