序列自动机(一个数组而已...)及经典例题

next[i][j]表示在原串si位后面的第一个j出现的位置,设串长为n,字符集大小为a,预处理时间复杂度为O(n*a),代码如下。

for(int i=n;i;i--)
{
	for(int j=1;j<=a;j++) next[i-1][j]=next[i][j];
	next[i-1][s[i]]=i;
}

求子序列个数代码如下(加上记忆化,以避免同样的状态被计算多次)。

int dfs(int x)
{
	if(f[x]) return f[x];
	for(r int i=1;i<=a;i++)
		if(next[x][i]) f[x][y]+=dfs(next[x][i]);
	return ++f[x][y];
}
//调用:dfs(0);

常用技巧(用于求回文子序列或两个串的公共子序列个数等):对两个串分别构造next数组,dfs时引入两个参数,代码如下。

int dfs(int x,int y)//表示目前字符是串1的第x位,串2的第y位
{
	if(f[x][y]) return f[x][y];
	for(r int i=1;i<=a;i++)
		if(next1[x][i]&&next2[y][i]) f[x][y]+=dfs(next1[x][i],next2[y][i]);
	return ++f[x][y];
}
//调用:dfs(0,0);

难(模板)题:求一个串的回文子序列个数(对10^9+7取模)。

样例输入:ababa

输出:7(回文子序列有aaaaaabbbababababbaababa,空串)

我们对原串和反串分别构造next数组,然后按照上面的方式进行dfs,但我们要根据回文的性质进行调整。首先,回文串分为奇和偶,dfs过程中,应始终保证x+y<=n+1,若x+y=n+1,此回文串为奇串,否则为偶串。此外,我们会发现一个严重的问题:dfs到一个偶串时,可能会有奇串被漏掉(如自动机只能找到abba,却忽略了aba)。因此,我们考虑手动加上被忽略的奇串,同时注意已经找到的奇串不能再加一次(如ababa),只要特判一下就可以了,代码如下。

int dfs(int x,int y)
{
	if(f[x][y]) return f[x][y];//记忆化
	for(int i=1;i<=26;i++)
		if(next1[x][i]&&next2[y][i])
		{
			if(next1[x][i]+next2[y][i]>n+1) continue;//x+y>n+1,状态不合法,不进行统计
			if(next1[x][i]+next2[y][i]<n+1) f[x][y]++;
			//满足x+y=n+1的奇串不会被漏掉,其他奇串需要特别统计
			f[x][y]=(f[x][y]+dfs(next1[x][i],next2[y][i]))%mod;
		}
	return ++f[x][y];
}

例题二:给定三个串ABC,求一个最长的AB的公共子序列S,要求CS的子序列 。

首先考虑像“常用技巧”一样的dfs方式,再加一个参数z表示当前字符是C的第z位,但这么做显然是错的,因为匹配过程中C的某些字符可能会被跳过(dfsz时,不能保证C的前z位都在S中),完成后的S不一定满足CS的子序列。如何修改?考虑对Cnext数组做一点改动,原理其实很简单,阅读下面的代码便知。

for(int i=1;i<=26;i++) nextc[n][i]=n;//字符集为26个字母,C长度为n
for(int i=0;i<n;i++)
{
	for(r int j=1;j<=26;j++) nextc[i][j]=i;
	nextc[i][c[i+1]]=i+1;
}

这样,参数z的含义也发生了变化,它表示C的前z位都已包含在S中。于是,当z=n时,我们就可以愉快地统计答案了。

猜你喜欢

转载自blog.csdn.net/pig_dog_baby/article/details/81145857