题目链接:
题目大意:
给出两个字符串将其合并,每次可从一个串的开头拿走一个元素接到新串末尾。在新串中我们定义同一种字符的权值(L)为其在新串中,第一次出现和最后一次出现的下表之差(e.g.在”ABBA”中,L(A)=3,L(B)=1)。而新串的权值为其中所有字符的权值之和。求:新串的权值最小为多少。
解题思路:
首先,这道题的状态不难定义,f[i][j]表示:第一序列中拿i个,第二个序列中拿j个的最小权值。
其次,状态转移也很好想:f[i][j] = min(f[i-1][j]+cost(i), f[i][j-1]+cost(j))
但,这道题难就难在快速求出每次操作的代价也就是cost(i)或cost(j)
对于如何求cost,我们可以先总体考虑已经得到的新串的权值:不论我现在将i还是j放入新串中,新串中已经出现过的且还没有结束的元素都会对答案产生+1的贡献。注意!已经出现过且还没结束是指这个字符在新串中出现了至少一次,且原来的两个串中的任意一个至少还有一个该字符没有被放如新串中。
所以,
我们可以设g[i][j]
来表示在f[i][j]
这个状态下新串中有多少个元素已经出现但还没结束。但我们如何去随着f状态的不同来更新g的值呢?比如拿走的i或j是一个新的元素,它会对后面的f值产生贡献,那我们就要++g;而如果拿走的是一个结尾元素,它已经不会再对f产生贡献了,我们就要把它减去,所以要–g。
为了快速知道一个元素是否是一个全新或结尾元素,我们可以预处理出s[2][26]和e[2][26]
来分别记录在原来的1、2串中每个元素第一次和最后一次出现的位置(下标)。
Code:
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 0x3f3f3f3f;
const int MAXN = 5005;
char S[3][MAXN];
int T, f[MAXN][MAXN];
int g[MAXN][MAXN];
int s[3][30], e[3][30];
int main(){
scanf("%d",&T);
while(T --){
memset(s, 0x3f, sizeof s);
memset(e, 0, sizeof e);
scanf("%s%s",S[1]+1,S[2]+1);
int n = strlen(S[1]+1);
int m = strlen(S[2]+1);
for(int i = 1; i <= n; ++ i){
int tmp = S[1][i]-'A';
if(s[1][tmp]==0x3f3f3f3f) s[1][tmp] = i;
e[1][tmp] = i;
}
for(int i = 1; i <= m; ++ i){
int tmp = S[2][i]-'A';
if(s[2][tmp]==0x3f3f3f3f) s[2][tmp] = i;
e[2][tmp] = i;
}
g[0][0] = 0;
for(int i = 1; i <= n; ++ i){
f[i][0] = f[i-1][0] + g[i-1][0] ;
g[i][0] = g[i-1][0];
int tmp = S[1][i]-'A';
if(s[1][tmp]==i&&s[2][tmp]>0) g[i][0] ++;
if(e[1][tmp]==i&&e[2][tmp]==0) g[i][0] --;
}
for(int i = 1; i <= m; ++ i){
f[0][i] = f[0][i-1]+g[0][i-1];
g[0][i] = g[0][i-1];
int tmp = S[2][i]-'A';
if(s[2][tmp]==i&&s[1][tmp]>0) g[0][i] ++;
if(e[2][tmp]==i&&e[1][tmp]==0) g[0][i] --;
}
for(int i = 1; i <= n; ++ i)
for(int j = 1; j <= m; ++ j){
int cmp1 = f[i-1][j]+g[i-1][j];
int cmp2 = f[i][j-1]+g[i][j-1];
if(cmp1 < cmp2){
f[i][j] = cmp1;
g[i][j] = g[i-1][j];
int tmp = S[1][i]-'A';
if(s[1][tmp]==i&&s[2][tmp]>j) g[i][j] ++;
if(e[1][tmp]==i&&e[2][tmp]<=j) g[i][j] --;
}
else{
f[i][j] = cmp2;
g[i][j] = g[i][j-1];
int tmp = S[2][j]-'A';
if(s[2][tmp]==j&&s[1][tmp]>i) g[i][j] ++;
if(e[2][tmp]==j&&e[1][tmp]<=i) g[i][j] --;
}
}
printf("%d\n",f[n][m]);
}
return 0;
}