转载加上自己的理解:https://www.cnblogs.com/pigzhouyb/p/12755540.html
题目大意:
两个字符串S和T,长度分别为n和m,还有一个空串A
问:每次可以删掉S的第一个字符,然后放到一个初始为空的字符串A的首部或尾部,求有多少种不同的方法使得最后T是A的前缀。
题目链接:https://codeforces.ml/problemset/problem/1336/C
解题思路:
可以发现A串的特性是向两边扩展,于是我们可以从A串这个特性来思考问题的解法?(一开始想的是线性直接递推)
对于不断两边扩展的问题需要想到区间dp!!!
- 设 f [ l ] [ r ] f[l][r] f[l][r]为 S S S的前 l − r + 1 l-r+1 l−r+1个字符前后拼接形成的 A A A串, A A A串的任意区间 [ L , R ] , R − L + 1 > = m [L,R],R-L+1>=m [L,R],R−L+1>=m的任意一个字符符合条件的方案数。这里的字符满足条件当且仅当 i > ∣ T ∣ i>|T| i>∣T∣或者 i < = ∣ T ∣ i<=|T| i<=∣T∣且 A A Ai= T T Ti
- 我们考虑方程的转移,对于一个l,r和区间的长度len=r-l+1
- 对于s[len]=t[i]或者i>m的情况,说明当前新加入的字母足以匹配可以成为开头或开头可以选择任意字母,那么就有:
f
[
i
]
[
j
]
+
=
f
[
i
+
1
]
[
j
]
f[i][j]+=f[i+1][j]
f[i][j]+=f[i+1][j](当
i
<
=
m
i<=m
i<=m时,
i
+
1
i+1
i+1~
j
j
j都符合的前
j
−
(
i
+
1
)
+
1
j-(i+1)+1
j−(i+1)+1个字符)
(当 i > m i>m i>m时,因为前面都符合题意条件,所以可以直接相加) - 对于s[len]=t[j]或者j>m的情况,同理: f [ i ] [ j ] + = f [ i ] [ j − 1 ] f[i][j]+=f[i][j−1] f[i][j]+=f[i][j−1]
AC代码:
#include<bits/stdc++.h>
#define Mod 998244353
using namespace std;
typedef long long ll;
char s[4000],t[4000];
ll f[4010][4010];
int main() {
scanf("%s%s",s+1,t+1);
int ls=strlen(s+1),lt=strlen(t+1);
for(int i=1; i<=ls; i++) f[i][i]=(i>lt||s[1]==t[i])*2;
//预处理长度为1的情况,因为第一个放的时候,放前面或者后面都一样但是方式不一样
//于是要乘2
for(int len=2; len<=ls; len++)
for(int l=1; l+len-1<=ls; l++) {
int r=l+len-1;
if(s[len]==t[l]||l>lt) f[l][r]=(f[l][r]+f[l+1][r])%Mod;
if(s[len]==t[r]||r>lt) f[l][r]=(f[l][r]+f[l][r-1])%Mod;
}
ll ans=0;
for(int i=lt; i<=ls; i++)
ans=(ans+f[1][i])%Mod;
printf("%lld\n",ans);
return 0;
}