Color Length UVA - 1625

题目链接:UVA - 1625 

这道题其实dp很容易想到,dp[i][j] = min(dp[i - 1][j] + dis, dp[i][j - 1] + dis'),但是dis和dis'却难以求出,如果为每个状态设置一个字母起始位置和当前位置则会造成时间上的浪费,我们不妨换一种思路,对于dis也用dp的方法求解。首先判断在t1,t2字符串内字符i的起始和末尾位置,然后判断对于dis[i][j] = dis[i - 1][j] + t1[i]是合并串中出现一个新字符? 1:0 - t1[i]在合并串中已经结束不会再出现? 1:0 或者 dis[i][j - 1] + t2[j]是合并串中出现一个新字符? 1:0 - t2[j]在合并串中已经结束不会再出现? 1:0, 因为对于dis[i][j],表示取前i和前j组成的字符串中已经出现且未结束的字符个数,所以无论从哪一个状态推到相同的一个状态的,字符串中已经出现且未结束的字符个数是一样的,所以用哪个方法得到dis[i][j]都无所谓。

那么,怎么判断是否在合并串中出现了新的字符呢?对于从dis[i - 1][j](从dis[i - 1][j] 到 dis[i][j]多添加了t1[i])那当然是在第一个字符串中t1[i]是t1中t1[i]字符的起始位置,且在t2中还未出现t1[i]字符,也就是t1_s[t1[i]] == i && t2_s[t1[i]] > j,对于从dis[i][j - 1]情况,是第二个字符串中出现的新字符t2[j]是t2中该字符的起始位置,但是t1未出现该字符,得到t2_s[t2[j]] == j && t1_s[t2[j]] > i。

对于是否已经结束,也是同理,都在两者中结束了。

这样就可以得到答案了。

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
char t1[5005],t2[5005];
int dp[2][5005], dis[2][5005];//dp当前状态组合成的字符串最小值,dis当前状态添加一个字符后需要增加的值,也就是当前状态未结束的字符集合的值,对于是2而不是n是因为最多只用到上一个和左边的,所以只保留两行即可
int t1_s[30], t1_e[30], t2_s[30], t2_e[30];//t1字符串的起始结束,t2字符串的起始结束
int main()
{
    int t, n, m, bin;
    scanf("%d", &t);
    for(int cnt = 0; cnt < t; cnt++)
    {
        scanf("%s%s", t1 + 1, t2 + 1);
        n = strlen(t1 + 1);
        m = strlen(t2 + 1);
        for(int i = 1; i <= n; i++)
            t1[i] = t1[i] - 'A';//将字符串处理成数字便于计算
        for(int i = 1; i <= m; i++)
            t2[i] = t2[i] - 'A';
//找到对于两个字符串,每个字符串的每个字符的起始和结束位置,如果没有则起始是无穷大,结束是0
        memset(t1_s, 0x3f, sizeof(t1_s));
        memset(t2_s, 0x3f, sizeof(t2_s));
        memset(t1_e, 0, sizeof(t1_e));
        memset(t2_e, 0, sizeof(t2_e));
        for(int i = 1; i <= n; i++)
        {
            t1_s[t1[i]] = min(t1_s[t1[i]], i);
            t1_e[t1[i]] = i;
        }
        for(int i = 1; i <= m; i++)
        {
            t2_s[t2[i]] = min(t2_s[t2[i]], i);
            t2_e[t2[i]] = i;
        }
        memset(dp, 0, sizeof(dp));
        memset(dis, 0, sizeof(dis));
        for(int i = 0; i <= n; i++)
        {
            bin = i & 1;//bin&1区分本行和上一行
            for(int j = 0; j <= m; j++)
            {
                if(i + j == 0)
                    continue;
                int v1 = INF, v2 = INF;
                if(i)
                    v1 = dp[bin ^ 1][j] + dis[bin ^ 1][j];
                if(j)
                    v2 = dp[bin][j - 1] + dis[bin][j - 1];
                dp[bin][j] = min(v1, v2);
                
                if(i)
                {
                    dis[bin][j] = dis[bin ^ 1][j];//从dis[i - 1][j]推到的dis[i][j],新添加的就是t[i],之前说过无论从哪个状态推出dis[i][j]都是一样的,默认i>0就从这个推出,否则从下面那个推出dis[i][j]
                    if(i == t1_s[t1[i]] && j < t2_s[t1[i]])//t1[i]这个字符刚刚在合并串中出现
                        dis[bin][j]++;
                    if(i == t1_e[t1[i]] && j >= t2_e[t1[i]])//t1[i]这个字符已经不会在合并串中出现了
                        dis[bin][j]--;
                }
                else if(j)
                {
                    dis[bin][j] = dis[bin][j - 1];
                    if(j == t2_s[t2[j]] && i < t1_s[t2[j]])
                        dis[bin][j]++;
                    if(j == t2_e[t2[j]] && i >= t1_e[t2[j]])
                        dis[bin][j]--;
                }
            }
        }
        printf("%d\n", dp[n & 1][m]);
    }
    return 0;
}

 

 

 
posted @ 2020-07-18 13:14  funforever  阅读(119)  评论(0编辑  收藏  举报