表示在原串第位后面的第一个出现的位置,设串长为,字符集大小为,预处理时间复杂度为,代码如下。
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);
常用技巧(用于求回文子序列或两个串的公共子序列个数等):对两个串分别构造数组,时引入两个参数,代码如下。
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);
难(模板)题:求一个串的回文子序列个数(对取模)。
样例输入:
输出:7(回文子序列有,,,,,,,,,空串)
我们对原串和反串分别构造数组,然后按照上面的方式进行,但我们要根据回文的性质进行调整。首先,回文串分为奇和偶,过程中,应始终保证,若,此回文串为奇串,否则为偶串。此外,我们会发现一个严重的问题:到一个偶串时,可能会有奇串被漏掉(如自动机只能找到,却忽略了)。因此,我们考虑手动加上被忽略的奇串,同时注意已经找到的奇串不能再加一次(如),只要特判一下就可以了,代码如下。
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];
}
例题二:给定三个串,,,求一个最长的,的公共子序列,要求是的子序列 。
首先考虑像“常用技巧”一样的方式,再加一个参数表示当前字符是的第位,但这么做显然是错的,因为匹配过程中的某些字符可能会被跳过(到时,不能保证的前位都在中),完成后的不一定满足是的子序列。如何修改?考虑对的数组做一点改动,原理其实很简单,阅读下面的代码便知。
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;
}
这样,参数的含义也发生了变化,它表示的前位都已包含在中。于是,当时,我们就可以愉快地统计答案了。