代碼如下:
/*
最優子結構:dp[i][j](1...i,1...j)合併後的minsum
子問題:dp[i][j-1],dp[i-1,j](合併最後一個字符是來自哪個字符串)
證明:最優解的最後一個元素必定來自兩個字符串的末尾,如果子問題不包含最優解,那麼說明dp[i][j-1],dp[i-1][j]不是最優解(矛盾)
具體實現:滾動數組記錄dp[i][j],並且記錄這個狀態下每個字符的最後出現位置,之後計算時,別的元素都不變,只邊最後位置
上面的辦法不行,因爲沒辦法只通過字符最後的位置,算出字符的L(c),(因爲開始位置不知道)
標程上的辦法就很好,用c[i][j]記錄在(1...i,1...j)中已經出現的字符並且還沒有結束的個數,就是增量,並且用滾動數組減少內存
同時預處理出字符一開始出現的位置和最後出現的位置
*/
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define maxn 5100
#define inf 1000000
int dp[2][maxn];
int c[2][maxn];
char s1[maxn];
char s2[maxn];
int sp[26],ep[26],sq[26],eq[26];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%s%s",s1+1,s2+1);
int n=strlen(s1+1);
int m=strlen(s2+1);
for(int i=0;i<26;i++)
{
sp[i]=maxn,sq[i]=maxn,ep[i]=0,eq[i]=0;
}
for(int i=1;i<=n;i++)
{
s1[i]-='A';
if(sp[s1[i]]==maxn)
{
sp[s1[i]]=i;
}
ep[s1[i]]=i;
}
for(int i=1;i<=m;i++)
{
s2[i]-='A';
if(sq[s2[i]]==maxn)
{
sq[s2[i]]=i;
}
eq[s2[i]]=i;
}
/*for(int i=0;i<26;i++)
{
printf("sp %c:%d,ep %c:%d,sq %c:%d,eq %c:%d\n",i+'A',sp[i],i+'A',ep[i],i+'A',sq[i],i+'A',eq[i]);
}*/
int t=0;
memset(dp,0,sizeof(dp));
memset(c,0,sizeof(c));
for(int i=0;i<=n;i++)
{
for(int j=0;j<=m;j++)
{
if(!i&&!j)
continue;
int v1=inf;
int v2=inf;
if(i) v1=dp[t^1][j]+c[t^1][j];
if(j) v2=dp[t][j-1]+c[t][j-1];
dp[t][j]=min(v1,v2);
if(i)
{
c[t][j]=c[t^1][j];
if(sp[s1[i]]==i&&sq[s1[i]]>j) c[t][j]++;//新出現
if(ep[s1[i]]==i&&eq[s1[i]]<=j) c[t][j]--;//結束
}
else if(j)
{
c[t][j]=c[t][j-1];
if(sq[s2[j]]==j&&sp[s2[j]]>i) c[t][j]++;
if(eq[s2[j]]==j&&ep[s2[j]]<=i) c[t][j]--;
}
// printf("%d ",dp[t][j]);
}
//printf("\n");
t^=1;
}
printf("%d\n",dp[t^1][m]);
}
return 0;
}