序列自動機(Se AM)的總結

序列自動機(Se AM)的總結

聽起來很高大上,實際上代碼簡單的不行。

剛剛學,整理一下原理和應用。

原理:用一個數組next[i][j]next[i][j]維護第ii位 (從下標11開始) 字符後 (也就是從第i+1i+1位開始)

離第ii位最近的字符爲ch=a+jch='a'+j的位置。

可能數組定義有點複雜,舉個例子就明白了。

string:abcdastring:abcda

對於第一個字符aa

nt[1][1]=5nt[1][1]=5,即離第一個字符aa最近的aa的位置是55

nt[1][2]=2,nt[1][2]=2,即離第一個字符aa最近的bb的位置是22

依次類推:nt[1][3]=3,nt[1][4]=4,nt[1][5]=0()nt[1][3]=3,nt[1][4]=4,nt[1][5]=0(不存在)\dots

這樣維護一個字符串有什麼好處呢,顯然對於尋找某個子序列或者子串的時候非常方便。

應用1.判斷字符串是否爲某一個字符串(文本串)的子序列或者子串。

ep:ep: 我們要給的 s=abcda,s=abcda, 待查詢的字符串 t=dat=da

初始位置p=0p=0。

對於t1=d,next[p][da]=4p=4next[p][aa]=5.t_1=d,next[p][d-'a']=4\rightarrow p=4\rightarrow next[p][a-'a']=5.完畢

十分方便。

時間複雜度:O(n+m)O(n+m),nn是文本串長度,mm是所以待查詢串總長度。

next[][]next[][]的兩種寫法。

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:求kk個字符串的公共子序列個數。

ep:k=2,string:a,b.ep:k=2,string:a,b..

根據定義我們可以設f[x][y]f[x][y]表示aa從第xx位開始和bb從第yy位的公共子序列個數。

顯然我們可以枚舉公共子序列分別爲a,b,c,za,b,c\dots,z的貢獻,然後求和。

根據next[][]next[][]數組的定義,我們可以的遞推式。

如果nexta[x][ch]&&nextb[y][ch],nx=nexta[x][ch],ny=nextb[y][ch].next_a[x][ch]\&\&next_b[y][ch],令nx=next_a[x][ch],ny=next_b[y][ch].

有狀態轉移方程:f[x][y]+=f[nx][ny]f[x][y]+=f[nx][ny]

然後不斷遞歸就行了,初始狀態是如果x,yx,y都不爲0,因爲他們的首字母相同,一個字母也構成了公共子序列,即f[x][y]++f[x][y]++

ep:stringa=abcd,stringb=bcep: string_a=abcd,string_b=bc

顯然公共子序列是:

(b,b)(c,c)(bc,bc)(b,b)\\(c,c)\\(bc,bc)

我們模擬一下遞歸過程:
dfs(0,0)dfs(2,1)dfs(3,2)dfs(0,0)\rightarrow dfs(2,1)\rightarrow dfs(3,2) 結束。

遞歸到最深處開始回溯:f[3][2]=1f[2][1]=2f[0][0]=3f[3][2]=1\rightarrow f[2][1]=2\rightarrow f[0][0]=3結束。

時間複雜度:O(n3+3n)O(n^3+3n)
如果還不理解,可以自己在多打印幾次遞歸。

例題:3個字符串的公共子序列個數

#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;
}

待補\dots\dots

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章