序列自動機(Se AM)的總結
聽起來很高大上,實際上代碼簡單的不行。
剛剛學,整理一下原理和應用。
原理:用一個數組維護第位 (從下標開始) 字符後 (也就是從第位開始)
離第位最近的字符爲的位置。
可能數組定義有點複雜,舉個例子就明白了。
對於第一個字符:
,即離第一個字符最近的的位置是。
即離第一個字符最近的的位置是。
依次類推:
這樣維護一個字符串有什麼好處呢,顯然對於尋找某個子序列或者子串的時候非常方便。
應用1.判斷字符串是否爲某一個字符串(文本串)的子序列或者子串。
我們要給的 待查詢的字符串 。
初始位置
對於完畢
十分方便。
時間複雜度:,是文本串長度,是所以待查詢串總長度。
求的兩種寫法。
void SeAM(){
int len=strlen(a+1);
for(int i=len;i;i--){
for(int j=0;j<26;j++) nt[i-1][j]=nt[i][j];
nt[i-1][a[i]-'a']=i;
}
}
/////////////////////
void SeAM(){
int len=strlen(a+1);
for(int i=len;i;nt[i-1][a[i]-'a']=i,i--)
memcpy(nt[i-1],nt[i],sizeof nt[i]);
}
例題1:判斷是否爲子序列
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first
#define se second
char a[N],b[N];
int nt[N][26],q;
void SeAM(){
int len=strlen(a+1);
for(int i=len;i;i--){
for(int j=0;j<26;j++) nt[i-1][j]=nt[i][j];
nt[i-1][a[i]-'a']=i;
}
}
int main(){
scanf("%s%d",a+1,&q);
SeAM();
while(q--){
scanf("%s",b+1);
int len=strlen(b+1),p=0,f=0;
for(int i=1;i<=len;i++){
p=nt[p][b[i]-'a'];
if(!p) {
f=1;break;
}
}
puts(f?"No":"Yes");
}
return 0;
}
例題2:判斷是否爲子串
和上面代碼其實是一樣的。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first
#define se second
char a[N],b[N];
int nt[N][26],tmp[26],q;
void SeAM(){
int len=strlen(a+1);
for(int i=len;i;nt[i-1][a[i]-'a']=i,i--)
memcpy(nt[i-1],nt[i],sizeof nt[i]);
}
int main(){
scanf("%s%d",a+1,&q);
SeAM();
while(q--){
scanf("%s",b+1);
int len=strlen(b+1),p=0,f=0;
for(int i=1;i<=len;i++){
p=nt[p][b[i]-'a'];
if(!p) {
f=1;break;
}
}
puts(f?"No":"Yes");
}
return 0;
}
例題3:判斷子序列並記數
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+5,M=1e6+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first
#define se second
int nt[N][26],n;
char a[N],b[N];
void SeAM(){
int len=strlen(a+1);
for(int i=len;i;nt[i-1][a[i]-'a']=i,i--)
memcpy(nt[i-1],nt[i],sizeof nt[i]);
}
int main(){
scanf("%s%d",a+1,&n);
SeAM();
int ans=0;
for(int i=1;i<=n;i++){
scanf("%s",b+1);
int len=strlen(b+1),p=0,ok=1;
for(int i=1;i<=len;i++){
p=nt[p][b[i]-'a'];
if(!p){
ok=0;
break;
}
}
if(ok) ans++;
}
printf("%d\n",ans);
return 0;
}
應用2:求個字符串的公共子序列個數。
.
根據定義我們可以設表示從第位開始和從第位的公共子序列個數。
顯然我們可以枚舉公共子序列分別爲的貢獻,然後求和。
根據數組的定義,我們可以的遞推式。
如果
有狀態轉移方程:
然後不斷遞歸就行了,初始狀態是如果都不爲0,因爲他們的首字母相同,一個字母也構成了公共子序列,即。
。
顯然公共子序列是:
我們模擬一下遞歸過程:
結束。
遞歸到最深處開始回溯:結束。
時間複雜度:
如果還不理解,可以自己在多打印幾次遞歸。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=150+5,M=1e6+5,inf=0x3f3f3f3f,mod=1e8;
#define mst(a) memset(a,0,sizeof a)
#define lx x<<1
#define rx x<<1|1
#define reg register
#define PII pair<int,int>
#define fi first
#define se second
char a[N],b[N],c[N];
int nta[N][26],ntb[N][26],ntc[N][26],q,f[N][N][N],n;
void SeAM(char *a,int nt[][26]){
int len=strlen(a+1);
for(int i=len;i;nt[i-1][a[i]-'a']=i,i--)
memcpy(nt[i-1],nt[i],sizeof nt[i]);
}
int dfs(int x,int y,int z){
if(f[x][y][z]) return f[x][y][z];
//printf("(%d,%d)\n",x,y);
for(int i=0;i<26;i++){
if(nta[x][i]&&ntb[y][i]&&ntc[z][i])
f[x][y][z]=(f[x][y][z]+dfs(nta[x][i],ntb[y][i],ntc[z][i]))%mod;
}
if(x&&y&&z) ++f[x][y][z];
return f[x][y][z]%mod;
}
int main(){
scanf("%d%s%s%s",&n,a+1,b+1,c+1);
SeAM(a,nta);
SeAM(b,ntb);
SeAM(c,ntc);
printf("%d\n",dfs(0,0,0));
return 0;
}
待補