看到后面例题9-21也用了这题的方法,把这题补上
这题:在放置颜色的时候,需要判断它是不是最后一个,然后需要找到这个颜色的开始位置。如果把每个颜色的开始位置记录在状态中,状态空间将会太大
但是每次放置颜色,如果有 a 种颜色还没有结束,那么这 a 种颜色结束的时候,与初始位置的距离肯定要+1。每种颜色开始和结束的位置我们可以事先算出,因此我们可以在状态转移的时候判断当前还有多少种颜色没有结束。
根据这个性质,我们可以在状态中只记录两个序列的当前位置。状态转移时,累加没有结束的颜色的种类数量。
我一开始预处理每个 i 和 j 对应的颜色种类数量,三层循环(i j 和颜色)速度太慢,TLE了。后来参考了代码仓库,改为了:每次转移,在(i-1,j)或(i,j-1)的基础上判断是否有颜色开始/结束来确定(i,j)的颜色种类数量。
另外,dp(i,j) 最好设计成“已经放置了第一列的前 i 个,第二排的前 j 个,剩下的代价是多少”,而不是“放置第一排的 i-n 和第二排的 j-n,代价是多少 ”。后者在判断颜色的开始和结束时比较不方便。
Run Time: 0.012s
#define UVa "LT9-8.1625.cpp" //
char fileIn[30] = UVa, fileOut[30] = UVa;
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
//Global Variables. Reset upon Each Case!
const int INF = 99999999, maxn = 5000 + 5, maxc = 30;
int T, n, m;
char s1[maxn], s2[maxn];
int start[maxc][2], end[maxc][2];
int d[maxn][maxn];
int c[maxn][maxn];
/
int main() {
scanf("%d", &T);
while(T--) {
scanf("%s%s", s1+1, s2+1);
n = strlen(s1+1);
m = strlen(s2+1);
memset(start, -1, sizeof(start));
memset(end, -1, sizeof(end));
for(int i = 0; i < 30; i ++)
start[i][0] = start[i][1] = maxn;
for(int i = 1; i <= n; i ++) {
int c = s1[i] - 'A';
if(start[c][0] == maxn) start[c][0] = i;
end[c][0] = i;
}
for(int i = 1; i <= m; i ++) {
int c = s2[i] - 'A';
if(start[c][1] == maxn) start[c][1] = i;
end[c][1] = i;
}
d[0][0] = 0;
c[0][0] = 0;
for(int i = 0; i <= n; i ++) {
for(int j = 0; j <= m; j ++) {
if(i && j) d[i][j] = min(d[i-1][j] + c[i-1][j], d[i][j-1] + c[i][j-1]);
else if(i) d[i][j] = d[i-1][j] + c[i-1][j];
else d[i][j] = d[i][j-1] + c[i][j-1];
if(i) {
c[i][j] = c[i-1][j];
int color = s1[i]-'A';
if(i == start[color][0] && j < start[color][1]) c[i][j] ++;
if(i == end[color][0] && j >= end[color][1]) c[i][j] --;
}
else if(j){
c[i][j] = c[i][j-1];
int color = s2[j]-'A';
if(j == start[color][1] && i < start[color][0]) c[i][j] ++;
if(j == end[color][1] && i >= end[color][0]) c[i][j] --;
}
}
}
printf("%d\n", d[n][m]);
}
return 0;
}