☆lightoj1013 Love Calculator 两种转移方程详解(LCS+方案数DP)

来源:互联网 发布:摇杆下载软件 编辑:程序博客网 时间:2024/05/08 13:51


1013 - Love Calculator
   PDF (English)StatisticsForum
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




题意:
       给出两个字符串s0,s1 (1)  包含子序列s0,s1的字符串的最小长度为多少;(2)有多少个这样的字符串;
思路:最短长度很简单想出来,就是两个字符串的总和相加,减去最长公共子序列就行,然后就转化成了给你一个字符串,让他包含s1,s2两个子序列,这个字符串有多少种组合方法,一开始想用组合数学做。。先确定一个子序列,另一个“插空”。。然后发现很麻烦。。后来百度学习了这个用递推式或者说DP做就行。。总的来说有两种状态转移,还是要都学习一下,脑中的dp套路跟思想太少了。。
法一:
  (1) 最小长度: |s0|+|s1|-最长公共子序列
          (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];
这个状态转移方程的理解:
如果这sj == sk  i这个位置就只能是一个字母了,没有选择,如果不相同,这个位置可以以sj结尾,也可以以sk结尾(因为只要求s1s2的相对顺序不变就行)所以就加上dp[i-1][j-1][k](以sj结尾),dp[i-1][j][k-1]以sk结尾,既然是dp,状态转移,所以肯定要有个初始值,对于一题就是
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还是被虐的少了啊

0 0