题目大意
给定了两个长度分别为n和m的字符串,只包含有大写字母.现在要合并两个字符串成一个,具体操作是依次从两个字符串里选取开头的字符,放入正在构造的字符串的末尾.给最终得到的字符串一个权值,计算方式是里面所有元素的第一个与最后一个的位置之差的总和.求所有方案中的最小值.
数据范围:
(1 leq n ,m leq 5000)
思路
从复杂度上来推断,大概是一个(O(nm))的复杂度.那么由于每次插入一个元素到末尾这件事是跟前面无关的,所以比较直接的想法就是(dp)来做.状态:(f[i][j])表示当前字符串A里用了1i这些字符,B里用了1j,所有的构造方案里当前权值之和的最小值是多少.但是这个状态没有保存第一个和最后一个字符具体在哪,这导致转移非常的困难.因为根本不能从这个状态里推断说当前这个字符具体有没有用,是不是最后一个.
梳理一下当前这个问题:这个状态只能知道"宏观"上用了哪一部分的字符,而不能知道具体用了谁.而每个元素的权的计算是跟具体在哪被使用有关的.不妨思考这样一个转换:有没有什么办法让单个元素的权值之和的计算变成一个总体的"宏观"的计算呢?
这个转换就是不直接计算每个元素具体要不要去增加权值,而是计算增加一个元素之后,对所有的和会有怎样的影响.具体来说,如果插入的一个新元素是s的话,当前这个s是A里的第一个,而且当前枚举的B里的位置还没走到第一个s,那么就要让权值增加一.如果当前这个s是A里的最后一个,并且当前枚举的B里的位置已经走到最后一个了,那么就要让权值减少一.这样维护的具体含义就是当这个元素放入序列的时候,所有已经出现过,但是还没走到最后一个的这些元素他们的权值都要增加一,因为你每加一个,这些已经出现过但没走到最后一位的,他们跟最后一位的距离就增加了一.
那么想到这个维护的思路之后,就可以发现整个题大概就做出来了.但是还要结合一下DP的具体过程来思考怎么写.下面说一下DP的设计部分.
状态设计
状态定义:(f[i][j])表示当前用了A里的1i部分的元素,B里用了1j部分的元素,所有构造方案里权值之和最小的是多少.
辅助的状态:(cost[i][j])同样表示用了哪些元素,产生的最小的权值之和是多少.
入口:(f[0][0] = cost[0][0] = 0) 其他元素均为正无穷
转移:
①当(i >1)时,可以用A[i]转移过来.上一个状态是(f[i - 1][j])以及(cost[i - 1][j])
转移方程:(f[i][j] = min(f[i][j],f[i - 1][j] + cost[i - 1][j])
同时在这一位还要计算一下(cost[i][j]).
假如当前这个新加入的元素是s,并且s是第一次出现在A里,并且s还没有在B里出现,那么就要对(cost[i][j])增加一个一.如果s是最后一次出现在A里,而且在B里的最后一个位置已经超过了那么说明s这个元素后面就不可能再出现了,让(cost[i][j])减少一就可以了.
②当(j>1)时,可以用B[j]转移过来,上一个状态是(f[i][j - 1])以及(cost[i][j - 1])那么同上镜像的写出方程以及维护(cost[i][j])的方程就比较简单了.
转移方程:(f[i][j] = min(f[i][j],f[i][j - 1] + cost[i][j - 1])
如果当前元素s是B里第一次出现,并且在A里没有出现过,那么就给(cost[i][j])增加一.如果是最后一次出现在B里,并且已经跨过了A里的最后一个位置,那么就给(cost[i][j])减少一.
出口:(f[n][m])
预处理的时候顺便求一下第一次以及最后一次出现的位置就可以了.
注意实现的时候,第一个出现的位置设置成正无穷,最后一个设置成-1.这个是有原因的,因为可能出现说有A里出现的元素s在B里不存在,这个时候对应进去两种情况必须要直接让他不满足,可以结合代码理解一下,当然特判也可以.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005;
char A[N],B[N];
int f[N][N],cost[N][N],occ[2][N][2];
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%s",A + 1);getchar();
scanf("%s",B + 1);getchar();
int n = strlen(A + 1),m = strlen(B + 1);
//occ
for(int i = 0;i <= 25;++i)
{
occ[0][i][0] = occ[1][i][0] = 0x3f3f3f3f;
occ[0][i][1] = occ[1][i][1] = -1;
}
for(int i = 1;i <= n;++i)
{
if(occ[0][A[i] - 'A'][0] == 0x3f3f3f3f)
occ[0][A[i] - 'A'][0] = occ[0][A[i] - 'A'][1] = i;
else occ[0][A[i] - 'A'][1] = i;
}
for(int i = 1;i <= m;++i)
{
if(occ[1][B[i] - 'A'][0] == 0x3f3f3f3f)
occ[1][B[i] - 'A'][0] = occ[1][B[i] - 'A'][1] = i;
else occ[1][B[i] - 'A'][1] = i;
}
f[0][0] = 0;
cost[0][0] = 0;
for(int i = 0;i <= n;++i)
{
for(int j = 0;j <= m;++j)
{
if(i == 0 && j == 0) continue;
f[i][j] = 0x3f3f3f3f;
if(i > 0)
{
f[i][j] = min(f[i][j],f[i - 1][j] + cost[i - 1][j]);
cost[i][j] = cost[i - 1][j];
int ap = A[i] - 'A';
int fA = occ[0][ap][0],fB = occ[1][ap][0];
if(i == fA && j < fB) ++cost[i][j];
int lA = occ[0][ap][1],lB = occ[1][ap][1];
if(i == lA && j >= lB) --cost[i][j];
}
if(j > 0)
{
f[i][j] = min(f[i][j],f[i][j - 1] + cost[i][j - 1]);
cost[i][j] = cost[i][j - 1];
int ap = B[j] - 'A';
int fA = occ[0][ap][0],fB = occ[1][ap][0];
if(i < fA && j == fB) ++cost[i][j];
int lA = occ[0][ap][1],lB = occ[1][ap][1];
if(i >= lA && j == lB) --cost[i][j];
}
}
}
printf("%d
",f[n][m]);
}
return 0;
}