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