/*SE:wn------王宁*/
这题是我理解原代码最久的题目——所以看到这篇文章的你可能也想了很久吧,加油加油。
今天我来给大家说一下刘汝佳大神在紫书中没有详细讲解的部分以及他的原代码中看上去好像不合逻辑的地方(其实是对的啦)。从正面推导我还解释不了——就是这个是怎么想出来的以及极其清晰的运作原理,这应该和dp的最优子结构有关系吧。
书中:d(i,j)是还需要多少费用
代码中:它指的是
在
之前已经在第一个车道移走前i个和在第二个车道移走前(j-1)个
或者
之前已经在第一个车道移走前i-1个和在第二个车道移走前j个
所产生的对增加L(c)必不可少的最少贡献,当然啦,d是由c累加而来的。
加上
因为加上这个元素(第一个车道的i或者第二个车道的j)所产生的对L(c)增加的贡献。
贡献的计算方式是:加上前面已经开始但还没结束的元素的个数。
因为对于这样的元素,每增加一个中间元素,它们距离自己的结束元素就都加了1,所以综合起来看就是加上这些元素的总个数,在代码中用c[i][j]来表示——这两端大体上就是对分析第二段的计算方法的解释
然后这个新加上的元素又会对这些元素的总个数产生影响(增、减,或者不变——不变也是影响),这时候就要更新了,详解见代码。
稍微注意点的就是dp[i][j]被他用dp[t][j]代替了,第一维用了滚动数组,c[i][j]也是。
这是我的代码(有注释),刘汝佳大神的放在最后。对于部分代码我觉得自己能思考一下就明白的就不写注释了。
/*SE:wn------王宁*/
#include
using namespace std;
const int maxn = 5000+5;
#define INF 2000000000;
int run,runs,i,j,m,n,t,v1,v2;
char A[maxn],B[maxn];
int a[maxn],b[maxn];
int sa[27],ea[27],sb[27],eb[27];
int dp[2][maxn];
int c[2][maxn];
int main()
{
scanf("%d",&runs);
for(run=1;run<=runs;run++){
scanf("%s",A+1); scanf("%s",B+1);
printf("%s\n",A+1); printf("%s\n",B+1);
m=strlen(A+1); n=strlen(B+1);
for(i=1;i<=m;i++) a[i]=A[i]-'A'+1; for(i=1;i<=n;i++) b[i]=B[i]-'A'+1;
memset(ea,0,sizeof(ea)); memset(eb,0,sizeof(eb));
for(i=1;i<=26;i++) sa[i]=maxn; for(i=1;i<=26;i++) sb[i]=maxn;
for(i=1;i<=m;i++){
/*这个元素在a中是第一次出现*/
if( sa[a[i]]==maxn ) sa[a[i]]=i;
ea[a[i]]=i;
}
for(i=1;i<=n;i++){
if( sb[b[i]]==maxn ) sb[b[i]]=i;
eb[b[i]]=i;
}
t=0; memset(c,0,sizeof(c)); memset(dp,0,sizeof(dp));
for(i=0;i<=m;i++)
{
for(j=0;j<=n;j++)
{
if(i==0&&j==0) continue;
v1=INF; 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);
/*对于到了i!=0的后期,为什么只要考虑i那一边的c[i][j]更新
首先你得承认不管如何排列,当前i个和前j个确定,c[i][j]的值都是一样的
能结束的点一定会结束,因为开始的点和结束的点都会出现。反之亦然。
所以按理来说从哪一方面入手都是一样的,c[t^1][j]或者c[t][j-1]
我这只能举个例子给你们感受一下,希望后来人有缜密些的证明
例如
AABB
CCDD
求c[2][3]
有两条路
c[1][3] c[2][2]
前者=2,后者=0
然后前者要减去1,后者要加上1
今日之成就,补往事之因果。
我的A进去,把之前开头的A产生的一单位的贡献给消去了。
而在c[2][2]中,A 和 A是早就已经消去了的。
同样,D在 c[2][2]的基础上加入做的贡献在 c[1][3] 早就成为事实
要从抽象的思维中想出这种方式是厉害的,给刘汝佳大神点个赞。 */
if(i){
c[t][j]=c[t^1][j];
//如果下面没有,那么这里的开头就是全局的开头,sb[相应元素]应该置为maxn,
if(sa[a[i]]==i&&sb[a[i]]>j) c[t][j]++;
//如果下面没有,那么这里的结束就是全局的结束,eb[相应元素]应该置为0
if(ea[a[i]]==i&&eb[a[i]]<=j) c[t][j]--;
} else if(j){
c[t][j]=c[t][j-1];
if(sb[b[j]]==j&&sa[b[j]]>i) c[t][j]++;
if(eb[b[j]]==j&&ea[b[j]]<=i) c[t][j]--;
}
}
t^=1;
}
printf("%d\n",dp[t^1][n]);
}
return 0;
}
// UVa1625 Color Length
// Rujia Liu
#include
#include
#include
using namespace std;
const int maxn = 5000 + 5;
const int INF = 1000000000;
char p[maxn], q[maxn]; // starts from position 1
int sp[26], sq[26], ep[26], eq[26]; // sp[i] start positions of character i in p
int d[2][maxn], c[2][maxn]; // c[i][j]: how many "incomplete" colors in the mixed sequence
int main() {
int T;
scanf("%d", &T);
while(T--) {
scanf("%s%s", p+1, q+1);
int n = strlen(p+1);
int m = strlen(q+1);
for(int i = 1; i <= n; i++) p[i] -= 'A';
for(int i = 1; i <= m; i++) q[i] -= 'A';
// calculate s and e
for(int i = 0; i < 26; i++) { sp[i] = sq[i] = INF; ep[i] = eq[i] = 0; }
for(int i = 1; i <= n; i++) {
sp[p[i]] = min(sp[p[i]], i);
ep[p[i]] = i;
}
for(int i = 1; i <= m; i++) {
sq[q[i]] = min(sq[q[i]], i);
eq[q[i]] = i;
}
// dp
int t = 0;
memset(c, 0, sizeof(c));
memset(d, 0, sizeof(d));
for(int i = 0; i <= n; i++){
for(int j = 0; j <= m; j++){
if(!i && !j) continue;
// calculate d
int v1 = INF, v2 = INF;
if(i) v1 = d[t^1][j] + c[t^1][j]; // remove from p
if(j) v2 = d[t][j - 1] + c[t][j - 1]; // remove from q
d[t][j] = min(v1, v2);
// calculate c
if(i) {
c[t][j] = c[t^1][j];
if(sp[p[i]] == i && sq[p[i]] > j) c[t][j]++;
if(ep[p[i]] == i && eq[p[i]] <= j) c[t][j]--;
} else if(j) {
c[t][j] = c[t][j - 1];
if(sq[q[j]] == j && sp[q[j]] > i) c[t][j]++;
if(eq[q[j]] == j && ep[q[j]] <= i) c[t][j]--;
}
}
t ^= 1;
}
printf("%d\n", d[t^1][m]);
}
return 0;
}