例题9-8 颜色的长度 UVa1625

1.题目描述:点击打开链接

2.解题思路:本题利用区间dp解决,但是本题是一道比较复杂的区间dp,做法不太容易理解,需要慢慢分析。首先,题目要求我们寻找一种合并的方式,使得最后的总代价最小,这里的总代价就是题目中所说的不同字母的L(i)值的和。然而本题不能按照以往的经验,把两个序列分别已经移动走了i和j个元素,还需要多少代价作为状态,因为这样的定义并不知道某个字符什么时候会结束,而当字符结束时候,又不记得它是什么时候第一次出现的。


本题正确的状态定义是:第一个序列移走i个元素和第二个序列移走j个元素时的最小代价。为了防止出现不知道字符何时开始以及何时结束的情况,我们需要进行一番预处理。用p,q分别表示两个字符串,sp[c],ep[c]表示在字符串p中,c字符第一次出现的下标和最后一次出现的下标。同样的可以定义sq[c],eq[c]。为了便于计算状态转移时候新增的代价,我们来考虑新增的代价来自哪里。根据题意,如果某个字符没有结束,那么新增加一个字符后,所有没有结束的字符的距离都要+1,这就提示我们,可以设置一个数组c[i][j],表示从p字符串移动i个字符,q字符串移动j个字符后,已经开始但是没有结束的字符的个数。这样,可以得到如下的状态转移方程:

dp(i,j)=min(dp(i-1,j)+c[i-1][j],dp(i,j-1)+c[i][j-1]);

因此,接下来的问题转化为如何计算c[i][j]?显然,c[i][j]的计算要根据d[i][j]的情况而变化,而且要用到之前设置的sp,sq,ep,eq数组。假设新的字符c来自p字符串,那么,首先令c[i][j]=c[i-1][j],如果这个字符c是首次出现的,即sp[c]==i且sq[c]>j,那么c[i][j]++,否则,如果c的加入意味着c字符从此结束,即ep[c]==i且eq[c]<=j,那么之后的c[i][j]就不能算入c字符了,所以c[i][j]--。如果新的字符c来自q字符串,依然可以同上处理。这样就完成了c[i][j]的计算。而且每次都要先更新d[i][j],再更新c[i][j]。


可见,所有的状态一共有O(NM)种,且状态转移只需要O(1)的时间,因此本题的时间复杂度是O(NM)。下面的代码利用了滚动数组优化。

3.代码:

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
#define rep(i,n) for(int i=0;i<(n);i++)
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;

const int N=5000+10;

const int INF=100000000;

char p[N],q[N];
int sp[26],sq[26],ep[26],eq[26];
int d[2][N],c[2][N];

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s%s",p+1,q+1);  //下标从1开始
        int n=strlen(p+1);
        int m=strlen(q+1);
        for(int i=1;i<=n;i++)p[i]-='A';//将输入的字符用整数替代
        for(int i=1;i<=m;i++)q[i]-='A';

        rep(i,26){sp[i]=sq[i]=INF;ep[i]=eq[i]=0;}
        for(int i=1;i<=n;i++)
        {
            sp[p[i]]=min(sp[p[i]],i);  //预处理第i个字符在p字符串的第一次出现的位置和最后出现的位置
            ep[p[i]]=i;
        }
        for(int i=1;i<=m;i++)
        {
            sq[q[i]]=min(sq[q[i]],i);  //同上
            eq[q[i]]=i;
        }

        int t=0;
        me(c);me(d);
        rep(i,n+1)
        {
            rep(j,m+1)
            {
                if(!i&&!j)continue;
                int v1=INF,v2=INF;
                if(i)v1=d[t^1][j]+c[t^1][j]; //新的字符来自p数组,得到的最小代价
                if(j)v2=d[t][j-1]+c[t][j-1]; //新的字符来自q数组,得到的最小代价
                d[t][j]=min(v1,v2);
					     //以下根据新字符来自p数组还是q数组更新c[i][j]
                if(i)   
                {
                    c[t][j]=c[t^1][j];
                    if(sp[p[i]]==i&&sq[p[i]]>j)c[t][j]++;
                    if(ep[p[i]]==i&&eq[p[i]]<=j)c[t][j]--;
                }
                else if(j)
                {
                    c[t][j]=c[t][j-1];
                    if(sq[q[j]]==j&&sp[q[j]]>i)c[t][j]++;
                    if(eq[q[j]]==j&&ep[q[j]]<=i)c[t][j]--;
                }
            }
            t^=1;  //注意:一定要在j跑完一轮后再更新t
        }
        printf("%d\n",d[t^1][m]); //由于i==n结束时又执行了一次t^=1,因此答案是d[t^1][m]
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值