Time Limit: 2 second(s) | Memory Limit: 32 MB |
Yes, you are developing a 'Love calculator'. The software would be quite complex such that nobody could crack the exact behavior of the software.
So, given two names your software will generate the percentage of their 'love' according to their names. The software requires the following things:
1. The length of the shortest string that contains the names as subsequence.
2. Total number of unique shortest strings which contain the names as subsequence.
Now your task is to find these parts.
Input
Input starts with an integer T (≤ 125), denoting the number of test cases.
Each of the test cases consists of two lines each containing a name. The names will contain no more than 30 capital letters.
Output
For each of the test cases, you need to print one line of output. The output for each test case starts with the test case number, followed by the shortest length of the string and the number of unique strings that satisfies the given conditions.
You can assume that the number of unique strings will always be less than 263. Look at the sample output for the exact format.
Sample Input | Output for Sample Input |
3 USA USSR LAILI MAJNU SHAHJAHAN MOMTAJ | Case 1: 5 3 Case 2: 9 40 Case 3: 13 15 |
(2) dp[i][j][k] 表示前i个字符恰好包含s0前j个字符,包含s1前k个字符的方案数;
若 s0[j] =s1[k] dp[i][j][k]=dp[i-1][j-1][k-1];
若 s0[j]!=s1[k] dp[i][j][k]=dp[i-1][j-1][k]+dp[i-1][j][k-1];
cnt[i][0][i] = cnt[i][i][0] = 1;
因为当前序列如果都是一个序列的组成的,肯定就是只有1种情况也就是=1
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn = 50;
typedef long long ll;
ll dp[maxn][maxn], cnt[maxn*2][maxn][maxn];
char s1[maxn], s2[maxn];
int main()
{
int t, ca = 1;
cin >> t;
while(t--)
{
cin >> s1 >> s2;
// cout << s1 << endl << s2 << endl;
memset(dp, 0, sizeof(dp));
memset(cnt, 0, sizeof(cnt));
int len1 = strlen(s1);
int len2 = strlen(s2);
for(int i = 1; i <= len1; i++)
for(int j = 1; j <= len2; j++)
{
if(s1[i-1] == s2[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
int l = len1 + len2 - dp[len1][len2];
cnt[0][0][0] = 1;
for(int i = 1; i <= l; i++)
{
cnt[i][0][i] = cnt[i][i][0] = 1;
for(int j = 1; j <= min(len1,i); j++)
for(int k = 1; k <= min(len2,i); k++)
{
if(s1[j-1] == s2[k-1])
cnt[i][j][k] = cnt[i-1][j-1][k-1];
else
cnt[i][j][k] = cnt[i-1][j-1][k] + cnt[i-1][j][k-1];
}
}
printf("Case %d: %d %lld\n", ca++, l, cnt[l][len1][len2]);
}
return 0;
}
法二:
解析:设两个字符串 A、B,长度为n、m。
最短长度显然就是 n+m-lcs(A,B)。
令f[i][j]为A的前i个字符和B的前j个字符组成的最短长度,dp[i][j]为个数
若A[i]=A[j],f[i][j] = f[i-1][j-1]+1,dp[i][j] = dp[i-1][j-1]
反之,分为f[i-1][j]和f[i][j-1]两个情况。
思路:dp[i][j]为构造a[1]−a[i] 和 b[1]−b[j]的最短长度,ans[i][j]表示在该状态下的方案数。
(1)a[i]==b[j]
dp[i][j]=dp[i−1][j−1]+1
ans[i][j]=ans[i−1][j−1]
(2)a[i]!=b[j]
1、dp[i−1][j]<dp[i][j−1]
dp[i][j]=dp[i−1][j]+1ans[i][j]=ans[i−1][j]
2、dp[i−1][j]>dp[i][j−1]
dp[i][j]=dp[i][j−1]+1ans[i][j]=ans[i][j−1]
3、dp[i−1][j]==dp[i][j−1]
dp[i][j]=dp[i][j−1]+1ans[i][j]=ans[i−1][j]+ans[i][j−1]
这个方案书状态转移方程式在构造最短序列的过程中进行的,当a[i] == b[i]的时候,同理上个方法,不一样时,dp[i−1][j]<dp[i][j−1] ,肯定要选一个最小的,就是dp[i−1][j],所以就舍弃了dp[i][j−1] 这种排列,所以ans[i][j]=ans[i−1][j] ,同理下一个,当dp[i−1][j]==dp[i][j−1] 时,这个位置,选i也行,选j也行,这就是两种情况了。。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
char s1[50],s2[50];
int n,m,f[50][50];
LL dp[50][50];
int main(){
int i,j,cas,T;
scanf("%d",&cas);
for(T = 1;T <= cas;T++){
scanf("%s%s",s1+1,s2+1);
n = strlen(s1+1);m = strlen(s2+1);
memset(dp,0,sizeof(dp));
memset(f,0,sizeof(f));
for(i = 0;i <= n;i++) f[i][0] = i,dp[i][0] = 1;
for(j = 0;j <= m;j++) f[0][j] = j,dp[0][j] = 1;
for(i = 1;i <= n;i++){
for(j = 1;j <= m;j++){
if(s1[i]==s2[j]){
f[i][j] = f[i-1][j-1]+1;
dp[i][j] = dp[i-1][j-1];
}else{
f[i][j] = min(f[i-1][j],f[i][j-1])+1;
if(f[i-1][j]<=f[i][j-1]) dp[i][j] += dp[i-1][j];
if(f[i-1][j]>=f[i][j-1]) dp[i][j] += dp[i][j-1];
}
}
}
printf("Case %d: %d %lld\n",T,f[n][m],dp[n][m]);
}
return 0;
}
法三:
解题思路:字符串的最短长度就是两个字符串的长度和-LCS(两个字符串)
接着是求这个字符串有多少种组成方式了
用ans[i][j][k]表示该字符串当前有i个字符,包含了第一个字符串的j个字符,第2个字符串的k个字符的种类数
如果s1[j] = s2[k]
ans[i + 1][j + 1][k + 1] += ans[i][j][k]
如果s1[j] != s2[k]
ans[i + 1][j][k + 1] += ans[i][j][k]
ans[i + 1][j + 1][k] += ans[i][j][k]
这个状态转移跟法一其实是一样的,只不过这个是向后面转移,当s1[j] = s2[k] 时,ans[i + 1][j + 1][k + 1] += ans[i][j][k] ,si+1跟sj+1都是一样的,没有选择
如果s1[j] != s2[k] ,ans[i + 1][j][k + 1] += ans[i][j][k] ,也就是说他把第i+1个设成是sj的情况,同理ans[i + 1][j + 1][k] += ans[i][j][k]
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 35;
char s1[N], s2[N];
int len1, len2, cas = 1;
int dp[N][N];
long long ans[N * 2][N][N];
void init() {
scanf("%s%s", s1 + 1, s2 + 1);
len1 = strlen(s1 + 1);
len2 = strlen(s2 + 1);
}
int LCS() {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= len1; i++)
for (int j = 1; j <= len2; j++) {
if (s1[i] == s2[j]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
return dp[len1][len2];
}
void solve() {
int len = len1 + len2 - LCS();
memset(ans, 0, sizeof(ans));
ans[0][0][0] = 1;
for (int i = 0; i <= len; i++)
for (int j = 0; j <= len1; j++)
for (int k = 0; k <= len2; k++)
if (ans[i][j][k]) {
if (s1[j + 1] == s2[k + 1]) ans[i + 1][j + 1][k + 1] += ans[i][j][k];
else {
ans[i + 1][j][k + 1] += ans[i][j][k];
ans[i + 1][j + 1][k] += ans[i][j][k];
}
}
printf("Case %d: %d %lld\n", cas++, len, ans[len][len1][len2]);
}
int main() {
int test;
scanf("%d", &test);
while (test--) {
init();
solve();
}
return 0;
}
这么多种转移方程,我竟然一个也没想到,只想到组合数学xjb乱搞。。dp还是被虐的少了啊