题意: 题目的意思是,给你两个长度分别为n和m的颜色序列(n,m<=5000)
都是由大写字母组成,要求按照顺序合并成同一个序列,即每次
可以把一个序列开头的颜色放在新序列的尾部
比如两个序列:GGBY 和 YRRGB至少有两种合并结果:
GBYBRYRGB 和 YRRGGBBYB 对于每一种颜色c,跨度L(c)是最大位置和
最小位置之差,
问题是: 找一种合并的方式使得L(c)的总和最小。
解题报告:
对于序列动态规划,我们常常用 dp[i][j] 来表示 第一个串1~i,第二个串1~j 的答案。
对于这道题,我们用类似的方法,用 dp[i][j] 来表示, 第一个串的前 i个元素, 第二个串的前j个元素,已经合并入队列的最小代价。 但是怎么转移呢,我们发现由于序列一直在变化,所以不好转移每个字母的L(C)。 lrj的书上写了一个巧妙的方法, 那就是如果一个字母没有结束(以后还可以向队列加入这种字母),那么它的L(c)值就还可以增加,因此我们不必等一个字母结束后在来计算,而可以在过程中计算。为了快速转移,我们增加一个数组w[i][j], 表示当第一个串前i个元素,第二个串前j个元素已经加入到队列中时,有w[i][j]种字母还没有结束。
dp[i][j]=min(dp[i-1][j]+w[i-1][j],dp[i][j-1]+w[i][j-1])
#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 5e3 + 7;
const int M = 30;
int dp[N], w[N][N], d[N][N];
int sA[M], eA[M], sB[M], eB[M];
char A[N], B[N];
int a[N], b[N];
int main(){
int T;
scanf("%d", &T );
while( T-- ){
scanf("%s%s", A+1, B+1);
int lenA=strlen(A+1), lenB=strlen(B+1);
for ( int i=1; i<=29; i++ ) sA[i]=sB[i]=eA[i]=eB[i]=0;
for ( int i=1; i<=lenA; i++ ) {
a[i]=A[i]-'A'+1;
if( !sA[a[i]] ) sA[a[i]]=i;
eA[a[i]]=i;
}
for ( int i=1; i<=lenB; i++ ) {
b[i]=B[i]-'A'+1;
if( !sB[b[i]] ) sB[ b[i] ]=i;
eB[ b[i] ]=i;
}
for ( int i=1; i<=27; i++ ) if( !sA[i] ) sA[i]=lenA+1;
for ( int i=1; i<=27; i++ ) if( !sB[i] ) sB[i]=lenB+1;
for ( int i=0; i<=lenA; i++ ){
for ( int j=0; j<=lenB; j++ ) {
w[i][j]=0;
if (i){
int x1=w[i-1][j];
if( i==eA[ a[i] ] && j>=eB[ a[i] ] ) x1--;
if( i==sA[ a[i] ] && j<sB[ a[i] ] ) x1++;
w[i][j]=max(w[i][j],x1);
}
if (j){
int x2=w[i][j-1];
if( j==eB[ b[j] ] && i>=eA[ b[j] ] ) x2--;
if( j==sB[ b[j] ] && i<sA[ b[j] ] ) x2++;
w[i][j]=max(w[i][j],x2);
}
}
}
for ( int i=1; i<=lenA; i++ )
for ( int j=1; j<=lenB; j++ )
dp[j]=min(dp[j]+w[i-1][j],dp[j-1]+w[i][j-1]);
for ( int i=0; i<=lenA; i++ )
for ( int j=0; j<=lenB; j++ ){
if( !i&&!j ) continue;
d[i][j]=(1<<21)-1;
if( i ) d[i][j]=min(d[i][j],d[i-1][j]+w[i-1][j]);
if( j ) d[i][j]=min(d[i][j],d[i][j-1]+w[i][j-1]);
}
printf("%d\n", d[lenA][lenB] );
}
}