f[i][j] i是表示要构造的文本串枚举到了第i个字母 j是表示0~m个状态 这个状态是指与模式串匹配了多少字符 也就是0~i文本串后缀与模式串0~j前缀是相同的
枚举第i+1个字符a~z 然后就包括了这个字符后 文本串0~i+1后缀能匹配模式串多少前缀了 就由u=j来往前跳到了另一个匹配状态 所以就是f[i+1][u]+=f[i][j] 因为原来就是第i个字符就匹配到了模式串的j个字符 然后枚举一次第i+1字符 状态变为u 就是由ij这个状态转移过来的
同时 在i固定下 是什么状态也是可以枚举的 j从0~m-1枚举来的
#include<iostream>
using namespace std;
const int mod=1e9+7;
int ne[52],f[52][52];
int main(){
int n,i,j;
string s;
cin>>n>>s;
s=" "+s;
int m=s.size();
m--;
for(i=2,j=0;i<=m;i++){
while(j&&s[i]!=s[j+1]) j=ne[j];
if(s[i]==s[j+1]) j++;
ne[i]=j;
}
f[0][0]=1;
for(i=0;i<n;i++){ //已经排好i位了
for(j=0;j<m;j++){
for(char c='a';c<='z';c++){
int u=j;
while(u&&c!=s[u+1]) u=ne[u];
if(s[u+1]==c) u++;
if(u<m) f[i+1][u]=(f[i+1][u]+f[i][j])%mod;
}
}
}
int res=0;
for(i=0;i<m;i++) res=(res+f[n][i])%mod;
cout<<res<<endl;
}
输入
2
AAA
AAG
AAAG
2
A
TG
TGAATG
4
A
G
C
T
AGT
0
几乎同样的思路 就是注意好初始化与更新方式 是取min 不用担心啥只修改了一部分 会不会没修改完全就取值更小了 其实这个第一个for就是可以作出这个限制 发现每一次只要不满足 那我就一定不会用它来更新i+1的状态 也就是说枚举到i+1填k这个字符的这个状态完全没有用 然后是把所有的每i位可以填的值枚举出来 没有管原串是怎么样 其实就是看我是这个字符 那原串相不相同 不相同就要霸道的一定给我修改 然后取min 这样最后其实就递推i得到了结果
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e4;
int tr[N][27],cnt[N],ne[N],id,q[N],f[1002][N];
string s;
int get(char a){
if(a=='A') return 0;
if(a=='G') return 1;
if(a=='C') return 2;
return 3;
}
void insert(){
int i,p=0;
for(i=0;i<s.size();i++){
int t=get(s[i]);
if(!tr[p][t]) tr[p][t]=++id;
p=tr[p][t];
}
cnt[p]++;
}
void build(){
int hh=0,tt=-1,i;
for(i=0;i<4;i++){
if(tr[0][i]) q[++tt]=tr[0][i];
}
while(hh<=tt){
int t=q[hh++];
for(i=0;i<26;i++){
int p=tr[t][i];
if(!p) tr[t][i]=tr[ne[t]][i];
else{
ne[p]=tr[ne[t]][i];
q[++tt]=p;
//因为后面是直接看tr编号的cnt值 所以如果是有以这个字符大串的后缀(不是这个字符串)为不合法序列的话 那要将ne的cnt赋给它
cnt[p]|=cnt[ne[p]];
}
}
}
}
int TT;
int main(){
int n,i,j,k;
while(cin>>n,n){
memset(tr,0,sizeof tr);
memset(ne,0,sizeof ne);
memset(cnt,0,sizeof cnt);
id=0;
for(i=0;i<n;i++) {cin>>s;insert();}
build();
cin>>s;
s=" "+s;
int m=s.size();
m--;
int type;
memset(f,0x3f,sizeof f);
f[0][0]=0;
for(i=0;i<m;i++){ //前i个字符都匹配好了(修改好了)
for(j=0;j<=id;j++){ //匹配到了tr中的j编号
for(k=0;k<4;k++){ //枚举AGCT
if(get(s[i+1])==k) type=0;else type=1; //type代表要不要修改
int p=tr[j][k]; //直接tr就是编号或者为跳到的地方 如果说
if(!cnt[p]) f[i+1][p]=min(f[i][j]+type,f[i+1][p]);
}
}
}
int res=0x3f3f3f3f;
//可以是全部0~id取最小 因为只要cnt不为0的不合法的地方不可能更新到 一直都是0x3f3f3f3f
for(i=0;i<=id;i++) res=min(res,f[m][i]);
if(res==0x3f3f3f3f) res=-1;
printf("Case %d: %d\n",++TT,res);
}
}
很多dp问题都可以这样做 就是前一位与这一位这个递推关系是不变的 那都可以表示成矩阵乘法来做
综合了kmp dp递推 状态机 矩阵乘法知识
#include<iostream>
#include<cstring>
using namespace std;
const int N=25;
int mod,n,m;
int ne[N],a[N][N];
string s;
void get_next(){
int i,j;
for(i=2,j=0;i<s.size();i++){
while(j&&s[i]!=s[j+1]) j=ne[j];
if(s[i]==s[j+1]) j++;
ne[i]=j;
}
}
void mul(int c[][N],int a[][N],int b[][N]){
static int t[N][N]; //可以省去每次调用都开空间的时间
memset(t,0,sizeof t);
int i,j,k;
for(i=0;i<m;i++)
for(j=0;j<m;j++)
for(k=0;k<m;k++) //二维*二维
t[i][j]=(t[i][j]+a[i][k]*b[k][j])%mod;
memcpy(c,t,sizeof t);
}
int qmi(int k){
int f[N][N]={1}; //初始化为1种方案
while(k){
if(k&1) mul(f,f,a);
mul(a,a,a);
k>>=1;
}
int res=0;
for(int i=0;i<m;i++) res=(res+f[0][i])%mod;
return res;
}
int main(){
int i;
cin>>n>>m>>mod;
cin>>s;
s=" "+s;
get_next();
//得到系数矩阵
for(i=0;i<m;i++){ //匹配了i个数
for(char j='0';j<='9';j++){
int p=i;
while(p&&s[p+1]!=j) p=ne[p];
if(s[p+1]==j) p++;
if(p<m) a[i][p]++; //可以好好模拟下为啥是这两个下标
} // 因为这个时候i不变 也就是行不变 第二维在变的是列 所以就是更新匹配了i个字符时 0~m-1的状态跳过去的系数为列
}
cout<<qmi(n)<<endl;
}