题文:见网页:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3852,(紫书p276)
题解:
这个题目统计答案,或者说状态的转移十分巧妙,我们想如果设dp[i][j]表示一号串到了第i位,二号串到了第j位的最小话费,显然还要记一下最开始放入这个字母的位置,十分不好写对不对,并且也状压不下。
那么我们可以考虑,对于一个状态dp[i][j],我们不最后来算他是多少,我们可以一边加,一边判断,看前面有那些字母已经出现了,并且后面还有这个字母,那么显然对于每个这种字母,都要有1的花费,所以,这个题目就变成了如何维护当前满足上述定义的字母的个数,这个我们主要分类讨论一下就可以了,具体实现看代码吧。
代码:(其实可以不用滚动数组)
#include<iostream> #include<stdio.h> #include<algorithm> #include<cstring> #include<stdlib.h> #define ll long long const int MAXN=5010; const int inf=1<<30; using namespace std; int dp[2][MAXN],c[2][MAXN]; int op1[27],ed1[27],op2[27],ed2[27]; int n,m; char x[MAXN],y[MAXN]; int main(){ int t;scanf("%d",&t); while(t--){ memset(dp,0,sizeof(dp)); memset(c,0,sizeof(c)); scanf("%s%s",x+1,y+1); int n=strlen(x+1),m=strlen(y+1); for(int i=1;i<=n;i++) x[i]-='A'; for(int i=1;i<=m;i++) y[i]-='A'; memset(op1,127,sizeof(op1)); memset(op2,127,sizeof(op2)); memset(ed1,0,sizeof(ed1)); memset(ed2,0,sizeof(ed2)); for(int i=1;i<=n;i++){ op1[x[i]]=min(op1[x[i]],i); ed1[x[i]]=i; } for(int i=1;i<=m;i++){ op2[y[i]]=min(op2[y[i]],i); ed2[y[i]]=i; } int now=0,last=1;dp[0][0]=0; for(int i=0;i<=n;i++){ now^=1,last^=1; memset(dp[now],0,sizeof(now)); memset(c[now],0,sizeof(now)); for(int j=0;j<=m;j++){ if(!i&&!j) continue; int v1=inf,v2=inf; if(j) v1=dp[now][j-1]+c[now][j-1]; if(i) v2=dp[last][j]+c[last][j]; dp[now][j]=min(v1,v2); if(i){ c[now][j]=c[last][j]; if(op1[x[i]]==i&&op2[x[i]]>j) c[now][j]++; if(ed1[x[i]]==i&&ed2[x[i]]<=j) c[now][j]--; } else if(j){ c[now][j]=c[now][j-1]; if(op2[y[j]]==j&&op1[y[j]]>i) c[now][j]++; if(ed2[y[j]]==j&&ed1[y[j]]<=i) c[now][j]--; } } } printf("%d ",dp[now][m]); } }