题目:
有两个颜色序列,现按照“每次可以把一个序列开头的颜色放到新序列尾部”的顺序合并成一个序列。对于每个颜色 c 而言,L(c) 等于最大位置和最小位置之差。找出一种方式使得所有 L(c) 的总和最小。
分析:
这道题最困难的地方就在于 L(c) 的计算,无法在 DP 的过程中很好的计算出 L(c) 。所以,我们只能在指标函数的计算方式上想办法,我们不等某个颜色全转移完后算它的 L(c) ,而是每次累加。换句话说,在把一个颜色移到最终序列之前,需要把所有“已经出现但还没结束”的颜色的 L(c)值加 1。更进一步地,因为并不关心每个颜色的 L(c),所以只需要知道有多少种颜色开始但尚未结束。
所以,用 c[i][j] 表示第一个序列的前 i 个和第二个序列的前 j 个有多少个“已出现但还没结束的”颜色。转移方程为: f[i][j]=min(f[i−1][j]+c[i−1][j],f[i][j−1]+c[i][j−1]) ,f[i][j]表示第一个序列取前 i 个第二个序列取前 j 个的最小和。
其实发现关于两个序列问题的dp转移方式通常都是如此,以最长公共子序列为基础,转移方式大抵都是这般。除了指标函数的计算方式外,这个题中数组c的处理值得学习,也是用了dp的方式更新c,没有提前全求出来,代码简洁思路清晰,模仿。最近dp+dp处理某些用的到东西的题型很多。
代码:
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
using namespace std;
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
const int MAXN = 5005;
const double EPS = 1e-8;
const int INF = 0x3f3f3f3f;
char s1[MAXN], s2[MAXN];
int sp[2][26], ep[2][26],c[MAXN][MAXN],f[MAXN][MAXN];
int n, m;
int main() {
int T;
cin >> T;
while (T--) {
scanf("%s%s", s1+1, s2+1);
n = strlen(s1+1);
m = strlen(s2+1);
ms(sp, INF);
ms(ep, 0);
for (int i = 1; i <= n; i++) s1[i] -= 'A';
for (int i = 1; i <= m; i++) s2[i] -= 'A';
for (int i = 1; i <= n; i++) {
sp[0][s1[i]] = min(i, sp[0][s1[i]]);
ep[0][s1[i]] = i;
}
for (int i = 1; i <= m; i++) {
sp[1][s2[i]] = min(i, sp[1][s2[i]]);
ep[1][s2[i]] = i;
}
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
if(!i && !j) continue;
int v1 = INF,v2 = INF;
if(i) v1 = f[i-1][j] + c[i-1][j];
if(j) v2 = f[i][j-1] + c[i][j-1];
f[i][j] = min(v1,v2);
if(i){
c[i][j] = c[i-1][j];
if(sp[0][s1[i]]==i && sp[1][s1[i]] > j) c[i][j] ++;
if(ep[0][s1[i]]==i && ep[1][s1[i]] <= j) c[i][j]--;
}else if(j){
c[i][j] = c[i][j-1];
if(sp[1][s2[j]] == j && sp[0][s2[j]] > i) c[i][j] ++;
if(ep[1][s2[j]] == j && ep[0][s2[j]] <= i) c[i][j]--;
}
}
}
printf("%d\n",f[n][m]);
}
return 0;
}