今天没有模拟赛,便心血来潮,想练练DP,然后就选了这道Noip 2015 Day2 T2,倒腾了3h。
这道题我开始想的做法太离谱了,结果搞了半天连样例都没过,没办法无奈看题解了,又由于对这道题理解不是很透彻,导致我看了半天题解也没看懂,后来静下心来看了半个小时终于恍然大悟。
正解应该是
O(knm)
的做法,最外层for循环枚举k表示用多少个子串,再枚举B子串的长度,最里层枚举A子串的长度。
f[k][i][j]
从
A
串前
f[k][i][j]={①f[k][i−1][j]+②f[k−1][i−1][j−1]+f[k][i−1][j−1]−f[k][i−2][j−1]f[k][i−1][j](a[i]==b[j])(a[i]!=b[j])}
分两部分计算:
①
f[k][i−1][j]
是用
A
的前
②当
a[i]==b[j]
的时候我们就不仅仅要转移
f[k][i−1][j]
了,还需要转移用
A
前(语文不好,没有什么辞藻了..),但是不能直接加
f[k][i−1][j−1]
,因为
f[k][i−1][j−1]
不仅仅代表着
a[i−1]==b[j−1]
的方案数,毕竟我们在算
f[k][i−1][j−1]
的时候也是由①②部分转移的,所以我们要减去①部分
f[k][i−2][j−1]
,最后得到的就是
f[k][i][j]
了.
代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#define MOD 1000000007
using namespace std;
typedef long long LL;
int s[2][1005][205],n,la,lb,i,j,k,u;
char a[1005],b[205];
int read()
{
int x=0;char c=getchar();
while(c<48||c>57) c=getchar();
while(c>=48&&c<=57) x=x*10+c-48,c=getchar();
return x;
}
void char_read(char x[])
{
int t=0;
char c=getchar();
while(c<'a'||c>'z') c=getchar();
while(c>='a'&&c<='z') x[++t]=c,c=getchar();
}
int main()
{
la=read();lb=read();n=read();//读入
char_read(a);
char_read(b);
for(i=0;i<=la;i++)
s[0][i][0]=1;
for(k=1;k<=n;k++)//代码主体
{
u=k%2;
for(i=0;i<=la;i++)
s[u][i][k-1]=0;//不要漏了把滚动数组清零!!防止在j=k时的第一次循环把s[u-2][i-1][j-1]加进去
for(j=k;j<=lb;j++)
{
for(i=j;i<=la;i++)
{
s[u][i][j]=s[u][i-1][j];
if(a[i]==b[j])
{
s[u][i][j]=(s[u^1][i-1][j-1]+s[u][i][j])%MOD;
s[u][i][j]=(s[u][i][j]+s[u][i-1][j-1])%MOD;
if(i>=2) s[u][i][j]=(s[u][i][j]-s[u][i-2][j-1]+MOD)%MOD;
}
}
}
}
printf("%d\n",s[n%2][la][lb]%MOD);
return 0;
}