原题:
子串和子序列
时间限制: 1 Sec 内存限制: 512 MB题目描述
输入两个字符串s和t,他们都仅由小写字母构成。令x是s的非空子串,y是t的非空子序列,问有多少种方式构造x和y,使得x与y相等(构造子串和子序列时,只要位置不一样,就算不同的构造方式),答案对1000000007(10^9+7)取模。
输入
第一行输入s,第二行输入t。
输出
输出对1000000007(10^9+7)取模后的答案。
样例输入
aa
aa
样例输出
5
提示
方案有:s[1]=t[1], s[2]=t[1], s[1]=t[2], s[2]=t[2], s[1]s[2]=t[1][t2] 共5种
样例输入2:
codeforces
forceofcode
样例输出2:
60
【数据规模和约定】
30%数据 1<=s,t字符串长度<=15
另有20%数据 字符串全部由同一个字母构成
100%数据 1<=s,t字符串长度<=5000
样例输入2:
codeforces
forceofcode
样例输出2:
60
【数据规模和约定】
30%数据 1<=s,t字符串长度<=15
另有20%数据 字符串全部由同一个字母构成
100%数据 1<=s,t字符串长度<=5000
题意:
找出字符串s的子串与t的子序列相同的所有情况数量(位置不同算不同的方案)。
我看到这道题,首先对子串和子序列的定义想了好久(我太菜了555)
度娘说:子串——串中任意个连续的字符组成的子序列称为该串的子串。
子序列——某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。
简单来说,子串就是在原字符串中截取一段连续的字符组成新的字符串,原题要求非空。子序列就是在原字符串中按顺序抽取几个字符组成新的字符串。
例如字符串dfgsthfhgfher,gsthfhg是它的子串,dfhgher是它的子序列。
看懂题目并企图打满分的我首先想到的就是dfs,枚举s中每一种情况,用dfs在t中搜索这个子串并统计数量。
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 #include<math.h> 5 using namespace std; 6 const int M=1e9+7; 7 string rec,str1,str2; 8 int len1,len2; 9 long long res,C[5005][5005];; 10 void dfs(int le,int ri,int pos) 11 //传入的三个元素分别代表正在搜索的字符的下标、子串最后的字符的下标、目前已搜索到pos位置 12 { 13 if(le>ri) { res=(res+1)%M; return; } 14 //如果目前正在搜索的字符是末尾下一个字符,代表子串前面字符都被搜索到,这个子串也是t的子序列,记录结果并返回 15 for(int i=pos;i<len2;i++)//从当前位置搜索到t的结尾 16 if(str2[i]==str1[le]) dfs(le+1,ri,i+1); 17 //若如果找到了正在查找的字符,就在剩下的s中查找下一个字符,或者舍弃这个字符不要继续查找当前字符 18 return; 19 } 20 int main() 21 { 22 cin>>str1>>str2; 23 len1=str1.length(),len2=str2.length(); 24 for(int i=0;i<len1;i++) 25 for(int j=i;j<len1;j++) dfs(i,j,0); 26 //枚举s的子串起点与终点(i,j) 27 printf("%lld",res); 28 return 0; 29 }
然后毫不意外的TLE了,只过了30分的基础分。
于是想不到正解的我开始骗20分的数据。
s和t都是由相同的字符组成,假设s长度为len1,t长度为len2,那么对于s而言,由一个字符组成的子串有len1个,对于t而言这样的子序列有len2个,总个数就是len1*len2;
同样的,由两个相同字符组成的子串有len-1个,对于t而言这样的子序列相当于在len2个字符中取2个,用组合,数量为C[len2][2],乘法原理可得总个数就是(len1-1)*C[len2][2]。
同理,由三个字符组成的子串与子序列的总情况个数为(len1-2)*C[len2][3]。
于是就有了我的50分代码(懒得注释)
1 #include<iostream> 2 #include<stdio.h> 3 #include<string.h> 4 #include<math.h> 5 using namespace std; 6 const int M=1e9+7; 7 string rec,str1,str2; 8 int len1,len2; 9 long long res,C[5005][5005];; 10 void dfs(int le,int ri,int x) 11 { 12 if(le>ri) { res=(res+1)%M; return; } 13 for(int i=x;i<len2;i++) 14 if(str2[i]==str1[le]) dfs(le+1,ri,i+1); 15 return; 16 } 17 void sovle() 18 { 19 for(int i=1;i<=len2;i++) 20 { 21 C[i][0]=C[i][i]=1; 22 for(int j=1;j<i;j++) 23 C[i][j]=(C[i-1][j]+C[i-1][j-1])%M; 24 } 25 for(int i=1;i<=min(len1,len2);i++) 26 res=(res+(len1-i+1)*C[len2][i])%M; 27 } 28 int main() 29 { 30 cin>>str1>>str2; 31 len1=str1.length(),len2=str2.length(); 32 if(len1<=15 && len2<=15) 33 { 34 for(int i=0;i<len1;i++) 35 for(int j=i;j<len1;j++) dfs(i,j,0); 36 } 37 else sovle(); 38 printf("%lld",res); 39 return 0; 40 }
后来看到了正解是DP,f[i][j]表示s第i个字符为结尾的子串与t前j个字符中的子序列相同的数量。
如果s与t相同位置的字符相同(即s[i]=t[j]),f[i][j-1]的方案都是f[i][j]的方案,s[i]和t[j]分别为s子串与t子序列单独为一个方案,在f[i-1][j-1]的所有方案中,末尾都添加字符s[i]构成新的方案,数量为f[i-1][j-1]
所以状态转移方程是f[i][j]=f[i][j-1]+f[i-1][j-1]+1或f[i][j]=f[i][j-1](tql)。
1 #include<iostream.h> 2 #include<stdio.h> 3 using namespace std; 4 const int M=1e9+7; 5 int n,m,f[5005][5005],ans; 6 char s[5005],t[5005]; 7 int main() 8 { 9 scanf("%s%s",s+1,t+1); 10 n=strlen(s+1),m=strlen(t+1); 11 for(int i=1;i<=n;i++) 12 for(int j=1;j<=m;j++) 13 if(s[i]==t[j]) 14 f[i][j]=(f[i][j-1]+f[i-1][j-1]+1)%M; 15 else f[i][j]=f[i][j-1]; 16 for(int i=1;i<=n;i++) ans=(ans+f[i][m])%M; 17 printf("%d",ans); 18 return 0; 19 }