题目大意输入两个序列,将两个序列合并成一个序列,要求原有的顺序不能改变。
思路
dp
首先要想到如果按题目要求合并序列可以每次把一个序列的开头放在合成序列的尾部,此时可以设计状态d(i,j) 表示当前第一个序列到
了第i个位置,第二个序列到了第j个位置的最小花费,最后的结果就是d(n,m) ,n是第一个序列的长度,m是第二个序列的长度。状态
转移方程为d(i+1, j) = min{d(i+1,j), d(i,j)+w(i+1,j)} 和 d(i, j+1) = min{d(i,j+1), d(i,j)+w(i,j+1)},和LCS问题类似。其中w(i+1,j)表示移动第
i+1转移所需要的代价,对当前所有的在序列中的字母进行判断,如果出现了该字母但还有字母没有放在新序列,则说明代价要加1。
通过预处理可以实现O(1)的判断。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5000 + 10;
const int INF = 1000000007;
char s[maxn], t[maxn];
int d[maxn][maxn];
pair<int,int> st[30], tt[30];
int n, m;
void make_table(pair<int, int> * table, char * a, int len){
for(int i = 1; i <= len; ++i){
int id = a[i] - 'A';
table[id].second = i;
}
for(int i = len; i >= 1; --i){
int id = a[i] - 'A';
table[id].first = i;
}
}
void init(){
for(int i = 0; i < 30; ++i){
st[i].first = tt[i].first = INF;
st[i].second = tt[i].second = -1;
}
for(int i = 0; i <= n + 1; ++i)
for(int j = 0; j <= m + 1; ++j)
d[i][j] = INF;
}
inline int w(int i, int j){
int sum = 0;
for(int k = 0; k < 26; ++k)
if((st[k].first <= i || tt[k].first <= j) &&
(st[k].second > i || tt[k].second > j))
sum++;
return sum;
}
int solve(){
n = strlen(s + 1), m = strlen(t + 1);
init();
make_table(st, s, n);
make_table(tt, t, m);
d[0][0] = 0;
for(int i = 0; i <= n; ++i)
for(int j = 0; j <= m; ++j){
if(i + 1 <= n) d[i + 1][j] = min(d[i + 1][j], d[i][j] + w(i + 1, j));
if(j + 1 <= m) d[i][j + 1] = min(d[i][j + 1], d[i][j] + w(i, j + 1));
}
return d[n][m];
}
int main()
{
int T; scanf("%d", &T);
while(T --){
scanf("%s%s", s + 1, t + 1);
printf("%d\n", solve());
}
return 0;
}