题目大意
输入两个长度分别为n和m的颜色序列,要求按顺序合并为一个序列,要求每次把序列开头的颜色方法新序列的尾部。
现在有一个衡量标准L(c),L(c)表示的是颜色c的跨度:最大位置和最小位置之差。
对于字符串
GBBY
和字符串
YRRGB
来说:
当合并为
YRRGGBBYB
的时候,其L(c)分别为
color | G | Y | B | R | Sum |
---|---|---|---|---|---|
L(c) | 1 | 7 | 3 | 1 | 12 |
思路
使用dp[i][j]
表示从S1中取出前i个字符,以及从S2中取出前j个字符,组成字符串,这个字符串L(c)的sum值。
那么可以得到以下状态转移方程:
for(int i = 0; i <= len1; i++)
{
for(int j = 0; j <= len2; j++)
{
//将S1[i]插入到dp[i-1][j]中
if(i>0)
dp[i][j] = min(dp[i][j],dp[i-1][j] + cnt[i-1][j]);
//将S2[j]插入到dp[i][j-1]中
if(j>0)
dp[i][j] = min(dp[i][j],dp[i][j-1] + cnt[i][j-1]);
}
}
cnt[i][j]表示从字符S1中取出前i个字符,从字符S2中取出前j个字符,组成的字符串中,已经出现但是没有结束的字母的个数。
//cnt[i][j]字符串S1[i] + S2[j] 中已经开始但是没有结束的字母的个数
for(int i = 0; i<= len1; i++)
{
for(int j = 0; j <= len2; j++)
{
for(int c = 0; c < 26; c++)
{
if((c_list[c].l1 <= i || c_list[c].l2 <= j) && (c_list[c].r1 > i || c_list[c].r2 > j) ) cnt[i][j]++;
}
}
}
代码
#include<stdio.h>
#include<cstring>
#include<set>
#define maxn 5005
#define INF 1<<30
#define min(a,b) (a>b)? b:a
#define max(a,b) (a>b)? a:b
using namespace std;
char S1[maxn];
char S2[maxn];
int dp[maxn][maxn];
int cnt[maxn][maxn];
struct Node
{
int l1;//字符串S1中字母i的左边界
int r1;//字符串S1中字母i的右边界
int l2;//字符串S2中字母i的左边界
int r2;//字符串S2中字母i的右边界
};
Node c_list[27];
int main ()
{
int n;
scanf("%d",&n);
while(n--)
{
scanf("%s",S1);
scanf("%s",S2);
int len1 = strlen(S1);
int len2 = strlen(S2);
for(int i = 0; i<26; i++)
{
c_list[i].l1 = maxn+1;
c_list[i].r1 = -1;
c_list[i].l2 = maxn+1;
c_list[i].r2 = -1;
}
for(int i = 0; i<len1; i++)
{
c_list[S1[i]-'A'].l1 = min(c_list[S1[i]-'A'].l1,i);
c_list[S1[i]-'A'].r1 = max(c_list[S1[i]-'A'].r1,i);
}
for(int i = 0; i<len2; i++)
{
c_list[S2[i]-'A'].l2 = min(c_list[S2[i]-'A'].l2,i);
c_list[S2[i]-'A'].r2 = max(c_list[S2[i]-'A'].r2,i);
}
for(int i = 0;i<26;i++){
c_list[i].l1 +=1;
c_list[i].r1 +=1;
c_list[i].l2 +=1;
c_list[i].r2 +=1;
//printf("%c [%d %d] [%d %d]\n",i+'A',c_list[i].l1,c_list[i].r1,c_list[i].l2,c_list[i].r2);
}
for(int i = 0; i <= len1; i++)
{
for(int j = 0; j <= len2; j++)
{
dp[i][j] = INF;
cnt[i][j] = 0;
}
}
dp[1][0] = 0;
dp[0][1] = 0;
//cnt[i][j]字符串S1[i] + S2[j] 中已经开始但是没有结束的字母的个数
for(int i = 0; i<= len1; i++)
{
for(int j = 0; j <= len2; j++)
{
for(int c = 0; c < 26; c++)
{
if((c_list[c].l1 <= i || c_list[c].l2 <= j) && (c_list[c].r1 > i || c_list[c].r2 > j) ) cnt[i][j]++;
}
}
}
/*
for(int i = 0;i<=len1;i++){
for(int j = 0; j <= len2; j++){
printf("%d\t",cnt[i][j]);
}
printf("\n");
}
*/
for(int i = 0; i <= len1; i++)
{
for(int j = 0; j <= len2; j++)
{
//将S1[i]插入到dp[i-1][j]中
if(i>0)
dp[i][j] = min(dp[i][j],dp[i-1][j] + cnt[i-1][j]);
//将S2[j]插入到dp[i][j-1]中
if(j>0)
dp[i][j] = min(dp[i][j],dp[i][j-1] + cnt[i][j-1]);
}
}
/*
printf("\n\n");
for(int i = 0; i <= len1; i++){
for(int j = 0; j <= len2; j++){
printf("%d\t",dp[i][j]);
}
printf("\n");
}
*/
printf("%d\n",dp[len1][len2]);
}
}
/*
GBBY
YRRGB
*/
Hit
字符串相关的dp问题,最好是将字符串的起点设置为1,而不是0,这样会省去很多-1或者是+1的边界处理问题。