提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
关于本题,只是自己关于DP的一点小小见解,如有问题,欢迎在评论区发表你尖锐的看法。
如果有更好的方法,欢饮在评论区讨论。
一、String Game题目描述
Clair和Bod在玩一个游戏。克莱尔写了一串小写字母,鲍勃在其中设置了这个谜题
通过选择一个他最喜欢的子序列作为游戏的关键词。但是鲍勃太笨了,他可能会写错一些字母。(可能存在这个子序列在Clair写的小写字母中没有出现)
现在Clair想知道Bod的字符串是否是克莱尔的字符串的子序列以及有多少次
Bob的字符串是否作为子序列出现在克莱尔的字符串中。因为答案可能很大,你应该
以10^9 + 7模输出答案。
(本题的 mod = 1000000007)
输入:
第一行是claire的字符串(长度不超过5000),第二行是Bob的字符串(长度不超过1000)。
输出:
输出一行,包括一个整数,表示Bob的字符串作为a出现的次数
Clair字符串中的子序列。答案应该对10^9+ 7取模。
样例1:
eeettt
et
结果: 9
样例2:
eeettt
te
结果
0
二、分析步骤
1.分析
我们一拿到这道题,这啥呀这是?
这是一道典型的动态规划题,当听到动态规划,当时的我已经慌了,真不知道怎么办。
但是DP的基本步骤是:
什么找最优子结构(大问题得到最好解,那么大问题的部分问题也是最好解决方案,如果存在更好解决方案,那么大问题得到更好解,但前提是大问题已经是最好解,你的部分问题的方案肯定是最好解了,如果存在更好,大问题会说,你礼貌吗?)
什么状态转移方程(这步虽难,但也是有鸡可循(哎哟,你干嘛),根据我的理解,是要从顶上往下看,这里的顶就是所要求解问题的答案,我们下面对这个展开)
其实分析部分上面没看懂,问题不大,但从这里开始就有点东西啦。
我们就题论题,再从本题总结规律(我们遵循马克思主义理论)
OK,我们看到我们要求解的问题,Clair的字符串是更长的,我们用n表示Clair字符串的长度
用m表示Bob字符串的长度
n = strlen(Clair) m = strlen(Bob)
我们定义一个数组dp[n][m],为什么名字叫dp,因为DP(Dynamic Programming,动态规划),所以DP
为什么dp后面跟着[n][m]因为n表是Clair字符串的长度,m表示Bob字符串的长度
题目要我们求Clair字符串中有多少个像Bob字符串的个数,即子串个数.
那我们就赋予dp[n][m]表示的是这个答案(即在长度为n的字符串里,出现长度为m的字符串,并且后面长度为m的字符串是前面长度为n的字符串的子串)
dp[n][m],一拿到,什么鬼?
但是不要着急,这里面是有条件的,比如它会给你Clair和Bob的字符串,没有他们的字符串,
哪来这个问题,哪来n和m呢?
因此,我们第一步是要将他们的字符串存起来,才有后面的东西(n,m,dp[n][m],以及这个题的答案)
关于一个算法题,我会把它抽象成如下形式:
输入就是要存东西,核心算法就是处理,输出就是printf
我们看到输入样例1:
eeettt et
dp[m][n]表示Clair前m个字符串有Bob前n个字符串这个子串的个数
此时我们要找递推方程,怎么找,一般找邻近的dp[m-1][n],dp[m-1][n-1],dp[m][n-1]
dp[m-1][n]:Clair前m-1个字符串有Bob前n个字符串这个子串的个数
dp[m-1][n-1]:Clair前m-1个字符串有Bob前n-1个字符串这个子串的个数
dp[m][n-1]:Clair前m个字符串有Bob前n-1个字符串这个子串的个数
Clari字符串简称为C串, Bob字符串简称为B串
Clari字符串前m个字符串简称为C(m)
Bob字符串前n个字符串简称为B(n)
当要求dp[m][n]时, 还要依据s[m]与t[n]的关系
s[m]==t[n] 时
dp[m][n]=dp[m-1][n]+dp[m-1][n-1]
我又把它理解成下面那个式子
dp[m][n] = dp[m-1][n] + dp[m-1][n-1]~t[n]
因为C(m-1)已经含有B(n),也是C(m)的一部分
至于C(m-1)含有B(n-1)时,也要加上,因为s[m]=t[n]
~表示在C(m-1)找到B(n-1)的个数后,连接t[n]后就是C(m)含有B(n)的个数
我感觉写的差不多了,如果你们觉得哪里还要进一步补充,或者可以进一步优化让大家更好理解,请在评论区留言。如有错误,请指正。
2.代码实现
当然,我就知道你喜欢跳到这个地方(^_^),无所谓,我会出手(写注释.)
代码如下:
//String Game(DP + 子串个数)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef long long ll; //给long long 一个简称ll
const int mod = 1e9 + 7;
int dp[5005][1005]; //全局变量,自动初始化为0
char s[5005];//用来存储Clair字符串
char t[1005];//用来存储Bob字符串
//Clair's String length <=5000
//Bob's String length <=1000
//所以上面的dp数组前一个大小为5000左右
//后一个大小为1000左右
//dp[m][n] 表示Clair前m个字符串含Bob前n个字符串的个数
//eg:
//Clair: eeettt (length=6)
//Bob: et (length=2)
//dp[3][1] = 3 Clair前3个字符串(eee)含有Bob前一个字符串e的个数为3
//再来几个
//dp[1][1] = 1 dp[2][1] = 2 dp[3][1] = 3
//dp[4][1] = 3 dp[5][1] = 3 dp[6][1] = 3
//dp[1][2] = 0 dp[2][2] = 0 dp[3][2] = 0
//dp[4][2] = 3 dp[5][2] = 6 dp[6][2] = 9
//我们要先找到递推方程
//第一步: 明白dp[m][n]的含义,Clair前m个字符串含Bob前n个字符串的个数
//第二步: dp[m][n] = dp[m-1][n](前面含有n的要加上) + dp[m-1][n-1](前面m-1含有n-1个它们再各自连接s
//[m]和t[n]也可以得到前m个Clair含有前n个Bob)
//递推方程 dp[m][n] = dp[m-1][n] + dp[m-1][n-1]
int main()
{
gets(s);//gets()获取一行字符串,不包括回车
gets(t);
int m = (int)strlen(s);
int n = (int)strlen(t);
//printf("m = %d\n",m);
//printf("n = %d\n",n);
//利用char s[m] 和 char t[n]更新求解dp[m][n]
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i-1]==t[j-1]) //s的第i个字符在数组里是s[i-1],s[0]是第一个字符
{
if(j==1) //如果是第一列,直接+1
{
dp[i][1] = dp[i-1][1] + 1;
}
else
{
dp[i][j] = dp[i-1][j] + dp[i-1][j-1];
}
}
else
{
dp[i][j] = dp[i-1][j];
}
dp[i][j] %= mod;//数据过大要mod
}
}
printf("%d",dp[m][n]);
return 0;
}
运行结果:
完结撒花(好耶!)
如果代码哪里有误,或则哪里可以改进,都可以写评论,动态规划给我的感觉就好像痛苦面具。
但是学会后,他就如同神力般助你如何思考万千变化世界的某些问题。
总结
最好的总结就是我写了总结。