题意:输入两个长度分别为n和m (n,m≤5000) 的颜色序列,要求按序列合并成同一个序列,即每次可以把一个序列开头的颜色放到新序列的尾部。例如,两个颜色序列GBBY和YRRGB,至少有两种合并结果:GBYBRYRGB和YRRGGBBYB。对于每个颜色c来说,其跨度 L(c) 等于最大位置和最小位置之差。例如,对于上面两种合并结果,每个颜色的 L(c) 和所有 L(c) 的总和如图所示。你的任务是找一种合并方式,使得所有 L(c) 的总和最小。(本段摘自《算法竞赛入门经典(第2版)》)
分析:
dp[i][j]表示两个序列分别走到i和j位置,所需最小的
L(c)
。则状态转移是从(i-1,j)和(i,j-1)转移来。可以先预处理出每个颜色在两个序列中开始和结束的位置。这样在状态转移的过程中可以做到
O(1)
的复杂度,总复杂度为
O(nm)
。
代码:
#include <iostream>
#include <algorithm>
#include <fstream>
#include <string>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <cctype>
#include <stack>
#include <set>
using namespace std;
const int maxn = 5000 + 5, INF = 1e9;
int T, la, lb, tmp1, tmp2;
int al[30], ar[30], bl[30], br[30];
int dp[maxn][maxn], cnt[maxn][maxn];
char a[maxn], b[maxn];
int main()
{
scanf("%d", &T);
for (int C = 0; C < T; ++C)
{
for (int i = 0; i < 26; ++i)
{
al[i] = INF;
bl[i] = INF;
ar[i] = 0;
br[i] = 0;
}
scanf("%s%s", a + 1, b + 1);
la = strlen(a + 1);
lb = strlen(b + 1);
for (int i = 1; i <= la; ++i)
{
al[a[i] - 'A'] = min(al[a[i] - 'A'], i);
ar[a[i] - 'A'] = max(ar[a[i] - 'A'], i);
}
for (int i = 1; i <= lb; ++i)
{
bl[b[i] - 'A'] = min(bl[b[i] - 'A'], i);
br[b[i] - 'A'] = max(br[b[i] - 'A'], i);
}
for (int i = 0; i <= la; ++i)
for (int j = 0; j <= lb; ++j)
{
if (!i && !j)
continue;
tmp1 = INF;
tmp2 = INF;
if (i)
tmp1 = dp[i - 1][j] + cnt[i - 1][j];
if (j)
tmp2 = dp[i][j - 1] + cnt[i][j - 1];
dp[i][j] = min(tmp1, tmp2);
if (i)
{
cnt[i][j] = cnt[i - 1][j];
if (al[a[i] - 'A'] == i && bl[a[i] - 'A'] > j)
++cnt[i][j];
if (ar[a[i] - 'A'] == i && br[a[i] - 'A'] <= j)
--cnt[i][j];
}
else if (j)
{
cnt[i][j] = cnt[i][j - 1];
if (bl[b[j] - 'A'] == j && al[b[j] - 'A'] > i)
++cnt[i][j];
if (br[b[j] - 'A'] == j && ar[b[j] - 'A'] <= i)
--cnt[i][j];
}
}
printf("%d\n", dp[la][lb]);
}
return 0;
}