orz好久不写博客了,主要是觉着有的题补上了就补上了没有写博客的必要……
dp还是一如既往地菜,大概是不会这种一步步拆贡献的dp,像极了不会把学习进程拆成一天天努力的我……
题目
T组样例,T未知
每组样例给两个仅大写字母构成的串,每个串长不超过5000,
现在要你把这两个串归并成一个新串,
新串中,每种字母对答案的贡献是其最后出现的位置-最开始出现的位置
求新串的最小代价和
思路来源
https://github.com/aoapc-book/aoapc-bac2nd/blob/master/ch9/UVa1625.cpp
题解
dp[i][j]代表第一个串到i第二个串到j的最小代价和
add[i][j]代表第一个串到i第二个串到j的时候,还有多少种字符对答案有贡献
对答案有贡献,即出现了开头,但还没出现结尾,
枚举这一次填i还是填j,显然从dp[i-1][j]+add[i-1][j]和dp[i][j-1]+add[i][j-1]转移而来
不管怎么算,add[i][j]都是一样的,与顺序无关
因为字符X有贡献的话,只有[i+1,n]∪[j+1,m]中出现X才可,这与[1,i][1,j]怎么合并顺序无关
5000的串长,dp[5000][5000]开不下,所以滚动数组
代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+10,INF=0x3f3f3f3f;
int t,n,m;
char s[2][N];
int dp[N][N],add[N][N];
int las,now,v,x,y,len[2],st[2][26],ed[2][26];
int main()
{
scanf("%d",&t);
while(t--)
{
for(int i=0;i<2;++i)
{
scanf("%s",s[i]+1);
len[i]=strlen(s[i]+1);
for(int j=0;j<26;++j)
{
st[i][j]=len[i]+1;
ed[i][j]=0;
}
for(int j=1;j<=len[i];++j)
{
v=s[i][j]-'A';
ed[i][v]=j;
}
for(int j=len[i];j>=1;--j)
{
v=s[i][j]-'A';
st[i][v]=j;
}
}
now=0;las=1;
dp[now][0]=add[now][0]=0;
dp[las][0]=add[las][0]=0;
for(int i=0;i<=len[0];++i)
{
for(int j=0;j<=len[1];++j)
{
if(!i&&!j)continue;
x=y=INF;
if(i)x=dp[las][j]+add[las][j];
if(j)y=dp[now][j-1]+add[now][j-1];
dp[now][j]=min(x,y);
if(i)
{
add[now][j]=add[las][j];
v=s[0][i]-'A';
if(st[0][v]==i&&st[1][v]>j)add[now][j]++;
if(ed[0][v]==i&&ed[1][v]<=j)add[now][j]--;
}
else if(j)
{
add[now][j]=add[now][j-1];
v=s[1][j]-'A';
if(st[1][v]==j&&st[0][v]>i)add[now][j]++;
if(ed[1][v]==j&&ed[0][v]<=i)add[now][j]--;
}
}
swap(now,las);
}
printf("%d\n",dp[las][len[1]]);
}
return 0;
}