例题9-8 颜色的长度 UVa1625

来源:互联网 发布:Servlet接收json gson 编辑:程序博客网 时间:2024/06/05 04:03

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]    }}

0 0