题目链接:https://vjudge.net/contest/306591#problem/H
题目大意:输入两个长度分别为n和m的颜色序列,要求按顺序合并成同一个序列,即每次可以把一个序列开头的颜色放到新序列的尾部。对于每个颜色c来说,其跨度L(c)等于最大位置和最小位置之差。应使得合并成的序列中所有颜色的L(c)之和尽量小。
思路:
无后效性,但是这个代价条件想不出来怎么搞。参看紫书,理解了他的基本思想,但还是码不出来,无奈又看了代码。
可以想到用d[ i ][ j ] : 表示A序列的前 i 个字符 并上 B序列的 j 个字符的最小代价。
可是这个代价怎么算?但从要知道他的跨度,就要知道他的始末位置,从状态里很难直接获得。这时需要转换计算代价的方式:不是等到一个颜色全部移动完之后再算,而是每次累加。当把某个颜色移动到最终序列前,需要把所有“已经出现但还没结束”的颜色的L(c)加一。这里的“还没结束”
指的是还没有到达这个颜色的最后位置,在最终序列中它可能已经出现了不止一次,但只要它还没有结束(后面还有),它就会给那些未结束的颜色代价加一,因为它横在了中间。
转:https://blog.csdn.net/CY05627/article/details/88369386
#include <bits/stdc++.h>
using namespace std;
const int INF = 1<<30;
const int N = 5005;
char s1[N], s2[N];
int L1[100], L2[100], R1[100], R2[100];
int d[N][N], num[N][N];
/* num[i][j] :取走序列1的前 i 个字符,取走序列2中前 j 个字符,在最终序列中已经出现但未结束的字符个数。 d[i][j]:在两个序列中分别取走了前 i , j 个字符的最小代价。 L1[],R1[]:分别表示序列1中每个字符的开始位置和结束位置。b2,e2一样。 向最终序列新增加一个字符后(这个字符来自序列1或2的头部),所有已经出现但未结束的字符的跨度L(c)都要加一。 状态转移方程:dp[i][j] = min( dp[i-1][j] + num[i-1][j],dp[i][j-1] + num[i][j-1] )。 */
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",s1+1,s2+1);
int Len1=strlen(s1+1);
int Len2=strlen(s2+1);
for(int i='A';i<='Z';i++)
{
L1[i]=L2[i]=INF;
R1[i]=R2[i]=0;
}
for(int i=1;i<=Len1;i++)
{
char c=s1[i];
L1[c]=min(L1[c], i);
R1[c]=max(R1[c], i);
}
for(int i=1;i<=Len2;i++)
{
char c=s2[i];
L2[c]=min(L2[c], i);
R2[c]=max(R2[c], i);
}
num[0][0]=0;
for(int i=0;i<=Len1;i++)
{
for(int j=0;j<=Len2;j++)
{
if(i)
{
num[i][j]=num[i-1][j];
char c=s1[i];
if(L1[c]==i&&L2[c]>j)//能确定s1[i]是第一字符c,因为s2[]中的第一个字符c还在s2中
{
++num[i][j];
}
if(R1[c]==i&&R2[c]<=j)//能确定s1[i]是最后字符c,因为s2[]中的最后字符已经拿出来了
{
--num[i][j];
}
}
if(j)
{
num[i][j]=num[i][j-1];
char c=s2[j];
if(L2[c]==j&&L1[c]>i)
{
++num[i][j];
}
if(R2[c]==j&&R1[c]<=i)
{
--num[i][j];
}
}
}
}
for(int i=0;i<=Len1;i++)
{
for(int j=0;j<=Len2;j++)
{
if(i==0&&j==0) continue;
d[i][j] = INF;
if(i) d[i][j] = min(d[i][j], d[i-1][j] + num[i-1][j]);
if(j) d[i][j] = min(d[i][j], d[i][j-1] + num[i][j-1]);
}
}
printf("%d\n",d[Len1][Len2]);
}
return 0;
}
//滚动数组优化
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int INF = 1<<30;
const int N = 5005;
char s1[N], s2[N];
int b1[100], b2[100], e1[100], e2[100];
int num[2][N], d[2][N]; // 滚动数组
int main()
{
//freopen("in.txt","r",stdin);
int T; scanf("%d",&T);
while(T--){
scanf("%s\n%s",s1+1, s2+1);
//printf("%s\n%s\n",s1+1,s2+1);
int len1 = strlen(s1+1), len2 = strlen(s2+1),c;
for(c = 'A'; c <= 'Z'; ++c) { b1[c] = b2[c] = INF; e1[c] = e2[c] = 0; }
for(int i = 1; i <= len1; ++i){
c = s1[i];
b1[c] = min(i, b1[c]);
e1[c] = i;
}
for(int i = 1; i <= len2; ++i){
c = s2[i];
b2[c] = min(i, b2[c]);
e2[c] = i;
}
memset(d,0,sizeof(d));
memset(num,0,sizeof(num));
int t = 0;
for(int i = 0; i <= len1; ++i){
for(int j = 0; j <= len2; ++j){
if(i==0&&j==0) continue;
int v1 = INF, v2 = INF;
if(i) v1 = d[t^1][j] + num[t^1][j];
if(j) v2 = d[t][j-1] + num[t][j-1];
d[t][j] = min(v1,v2);
if(i){
num[t][j] = num[t^1][j];
c = s1[i];
if(b1[c] == i&& b2[c] > j) ++num[t][j];
if(e1[c] == i&& e2[c] <= j) --num[t][j];
}
if(j){
num[t][j] = num[t][j-1];
c = s2[j];
if(b2[c] == j&& b1[c] > i) ++num[t][j];
if(e2[c] == j&& e1[c] <= i) --num[t][j];
}
}
t^= 1;
}
printf("%d\n",d[t^1][len2]);
}
return 0;
}