UVa 1625 颜色的长度(dp)
题意:
输入两个长度分别为n和m的颜色序列,要求按顺序合并成同一个序列,即每次可以把一个序列开头的颜色放到新序列的尾部。对于每个颜色c来说,其跨度L(c)等于最大位置和最小位置之差。
解析:
比较难的一道dp,反正我是想了好久都想不出来,这里参考了网上的思路,具体看代码注释
#include <bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f
#define pll pair<ll,ll>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rep1(i,a,b) for(int i=a;i>=b;i--)
#define rson rt<<1|1,m+1,r
#define lson rt<<1,l,m
using namespace std;
const int N=5e3+100,M=27;
/*
c[i][j]表示在字符串1中移走了i个字符
在字符串2中移走了j个字符
在主字符串中已经开始还未结束的字符个数
dp[i][j]表示两个序列已经分别移走了i和j个元素时的最小代价。
s1,e1数组分别用来表示序列1中每个字母的开头位置和结束位置
s2,e2分别用来表示序列1中每个字母的开头位置和结束位置。
新增一个字符后,所有已经出现的但没有结束的字符的跨度L(c)都要+1
则状态转移方程为
dp(i,j)=min(dp(i-1,j)+c[i-1][j],dp(i,j-1)+c[i][j-1])。
*/
int c[N][N],s1[M],e1[M],s2[M],e2[M],dp[N][N];
char ch1[N],ch2[N];
int main()
{
int t,n,m ;
cin>>t;
while(t--)
{
string str1,str2;
cin>>str1>>str2;
n=str1.size(),m=str2.size();
for(int i=n;i>0;i--)
str1[i]=str1[i-1];
for(int i=m;i>0;i--)
str2[i]=str2[i-1];
memset(s1,inf,sizeof s1);
memset(s2,inf,sizeof s2);
memset(e1,0,sizeof e1);
memset(e2,0,sizeof e2);
/*注意:这样的初始化是方便c数组的求解
不同的初始化c数组的求解方式也不同*/
for(int i=1;i<=n;i++)
{
int a=(int)str1[i]-'A';
s1[a]=min(s1[a],i);
e1[a]=i;
}
for(int j=1;j<=m;j++)
{
int a=(int)str2[j]-'A';
s2[a]=min(s2[a],j);
e2[a]=j;
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
int a=(int)str1[i]-'A';
int b=(int)str2[j]-'A';
if(i)
{
c[i][j]=c[i-1][j];
if(s1[a]==i&&s2[a]>j) c[i][j]++;//新出现的字符
if(e1[a]==i&&e2[a]<=j) c[i][j]--;//要结束的字符
}
if(j)
{
c[i][j]=c[i][j-1];
if(s2[b]==j&&s1[b]>i) c[i][j]++;
if(e2[b]==j&&e1[b]<=i) c[i][j]--;
}
}
}
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
if(!i&&!j) continue;
int v1=inf,v2=inf;
if(i) v1=dp[i-1][j]+c[i-1][j];
if(j) v2=dp[i][j-1]+c[i][j-1];
dp[i][j]=min(v1,v2);
}
}
cout<<dp[n][m]<<endl;
}
return 0;
}