这是填坑行动的第二篇-字符串DP。
板子题
题目描述
DNA \operatorname{DNA} DNA分子是人类遗传信息的载体,它间接地指导蛋白质的合成。 DNA \operatorname{DNA} DNA分子是由四种核苷酸组成的长链,这四种核苷酸分别是腺嘌呤核苷酸(用 A \operatorname{A} A代表)、鸟嘌呤核苷酸(用 G \operatorname{G} G代表)、胞嘧啶核苷酸(用 C \operatorname{C} C代表)和胸腺嘧啶核苷酸(用 T \operatorname{T} T代表)。习惯上用一个字符集为 { A , T , C , G } \{ \operatorname{{A,T,C,G}} \} {A,T,C,G} 的字符串来表示一个 DNA \operatorname{DNA} DNA分子序列,如 CGTTAGA \operatorname{CGTTAGA} CGTTAGA。
在生物进化过程中,
DNA
\operatorname{DNA}
DNA分子可能发生各种各样的突变。这种突变形成了生物遗传信息的改变,从而使生物得以分化,构成了生物的多样性。
主要的突变有三种:
在一个
DNA
\operatorname{DNA}
DNA序列中插入一个新的核苷酸,
DNA
\operatorname{DNA}
DNA序列中丢失了一个核苷酸,
DNA
\operatorname{DNA}
DNA序列中的某个核苷酸被另一个核苷酸所取代。
所谓两个
DNA
\operatorname{DNA}
DNA序列的一个比对是寻找一种排列方式,使得两个
DNA
\operatorname{DNA}
DNA序列在同样的位置上有相同的核苷酸,而若在同样的位置上两个DNA序列的核苷酸不同,则是由三种突变之一得到。例如,对两个
DNA
\operatorname{DNA}
DNA序列
T
1
=
ATCAG
T_1=\operatorname{ATCAG}
T1=ATCAG
T
2
=
ACTAG
2
T_2 =\operatorname{ACTAG}2
T2=ACTAG2,可以按如下方式比对,(
−
-
− 表示空白)
比对一:
T 1 T_1 T1 | T 2 T_2 T2 |
---|---|
A A A | A A A |
T T T | − - − |
C C C | C C C |
A A A | A A A |
− - − | T T T |
T T T | T T T |
也可以按如下方式比对
比对二:
T 1 T_1 T1 | T 2 T_2 T2 |
---|---|
A A A | A A A |
T T T | C C C |
C C C | A A A |
A A A | T T T |
T T T | T T T |
如果两个
DNA
\operatorname{DNA}
DNA序列在相同的位置上有越多相同的核苷酸对,则表明它们之间越相似,即它们存在功能上的相似性和进化史上的亲缘关系。
对于两个
DNA
\operatorname{DNA}
DNA序列的一个比对,规定如下得分方式:
一个同样的位置上有相同的核苷酸对,则可得
1
1
1分;
一个同样的位置上有不同的核苷酸对,则得
0
0
0分;
如果在某个位置上一个序列有核苷酸,而另一个序列在该位置上为
−
-
−,则得
−
2
-2
−2 分。例如,比对一的得分是
0
0
0分,比对二的得分是
3
3
3分。
问题是:对于两个
DNA
\operatorname{DNA}
DNA序列,寻找一种比对方式,使得它们的得分最高。
输入格式
输入数据由文件名为INPUT.*的文本文件提供,共有2行。
第1行为DNA序列T1 , 第2行为DNA序列T2 。序列的长度不大于1000。序列中的字母是英文小写或者大写字母。
输出格式
程序运行结束时,在屏幕上输出所找出的最大的得分
输入输出样例
输入#1
Atcag
Actag
输入#2
3
算法理解
DP,说实话是普及组的一个坎,我们可以简单叙述一下DP的本质。
一、区间DP
我们可以用
f
i
,
j
f_{i,j}
fi,j来表示区间
[
i
,
j
]
\left[ i,j \right]
[i,j]的答案,然后我们就可以根据题意,用
f
i
−
1
,
j
,
f
i
,
j
−
1
,
f
i
−
1
,
j
−
1
f_{i-1,j},f_{i,j-1},f_{i-1,j-1}
fi−1,j,fi,j−1,fi−1,j−1之类的来进行类推,有些时候我们甚至会用到
3
3
3维甚至
3
3
3维以上的数组,当然可以滚动,但是这里就不说了。
二、线性DP
我们可以用
f
i
f_i
fi可以表示区间
[
1
,
i
]
\left[ 1,i \right]
[1,i]的答案,然后我们可以根据题意,用
f
i
−
1
,
f
i
−
2
f_{i-1},f_{i-2}
fi−1,fi−2之类的来进行递推。
三、背包DP
待填坑
四、字符串背包
就是放在字符串上而已罢了。
敲重点,DP的精髓就在于方程式转移和背包DP!!!
题目解析
既然是DP题,那么我们就开始推DP式吧。
令
f
i
,
j
f_{i,j}
fi,j表示第一个字符串长度为
i
i
i,第二个字符串长度为
j
j
j的时候,答案是多少。
那么,如果
s
1
i
=
s
2
j
s1_i=s2_j
s1i=s2j的时候,我们就可以得出
f
i
,
j
=
f
i
−
1
,
j
−
1
+
1
f_{i,j}=f_{i-1,j-1}+1
fi,j=fi−1,j−1+1
当然,我们也可以推出另外两个方程转移式:
f
i
,
j
=
f
i
−
1
,
j
−
2
f_{i,j}=f_{i-1,j}-2
fi,j=fi−1,j−2
f
i
,
j
=
f
i
,
j
−
1
−
2
f_{i,j}=f_{i,j-1}-2
fi,j=fi,j−1−2
综合可得:
f
i
,
j
=
max
{
f
i
,
j
+
1
s
1
i
=
s
2
j
f
i
−
1
,
j
−
2
f
i
,
j
−
1
−
2
f_{i,j}=\operatorname{max} \begin{cases} f_{i,j}+1& s1_i=s2_j\\ f_{i-1,j}-2\\ f_{i,j-1}-2 \end{cases}
fi,j=max⎩⎪⎨⎪⎧fi,j+1fi−1,j−2fi,j−1−2s1i=s2j
以下为一堆代码、代码改进思路。
因为太蒟了所以会有一些没什么用的东西
长代码警告。
#include<cstdio>
#include<cstring>
#include<iostream>
#define max(a,b) ((a)>(b)?(a):(b))
#define maxn 1039
using namespace std;
char s1[maxn],s2[maxn];
int f[maxn][maxn];
int n1,n2;
int i,j,k;
int maxx;
int main(){
cin>>s1>>s2;
n1=strlen(s1); n2=strlen(s2);
for(i=0;i<n1;i++)
if(s1[i]<'a')
s1[i]+=32;
for(i=0;i<n2;i++)
if(s2[i]<'a')
s2[i]+=32;
if(s1[0]==s2[0])
f[0][0]=1;
else f[0][0]=0;
for(i=0;i<n1;i++)
for(j=0;j<n2;j++){
if(i==j&&i==0) continue;
maxx=f[i-1][j-1];//赋初值
if(s1[i]==s2[j])//条件
maxx=max(f[i-1][j-1]+1,maxx);//前一个满足条件就+1
if(s1[i-1]==s2[j])//条件
maxx=max(f[i-1][j]-2,maxx);//空白-2
if(s1[i]==s2[j-1])
maxx=max(f[i][j-1]-2,maxx);//空白-2
f[i][j]=maxx;
}
printf("%d",f[n1-1][n2-1]);
return 0;
}
结果:
仅仅过了样例…
我们来分析一下,如果是空白的,其实是不需要条件的,所以我们改一下代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#define max(a,b) ((a)>(b)?(a):(b))
#define maxn 1039
using namespace std;
char s1[maxn],s2[maxn];
int f[maxn][maxn];
int n1,n2;
int i,j,k;
int maxx;
int main(){
cin>>s1>>s2;
n1=strlen(s1); n2=strlen(s2);
for(i=0;i<n1;i++)
if(s1[i]<'a')
s1[i]+=32;
for(i=0;i<n2;i++)
if(s2[i]<'a')
s2[i]+=32;
f[0][0]=0;
for(i=1;i<n1;i++)
f[i][0]=f[i-1][0]-2;
for(i=1;i<n2;i++)
f[0][i]=f[0][i-1]-2;
for(i=1;i<n1;i++)
for(j=1;j<n2;j++){
maxx=f[i-1][j-1];
if(s1[i]==s2[j]) maxx=max(f[i-1][j-1]+1,maxx);
//if(s1[i-1]==s2[j])
maxx=max(f[i-1][j]-2,maxx);
//if(s1[i]==s2[j-1])
maxx=max(f[i][j-1]-2,maxx);
f[i][j]=maxx;
}
printf("%d",f[n1-1][n2-1]);
return 0;
}
但是依然没有AC:
我们尝试自己造一组数据:
输入:
abc
abd
输出:
1
我们会发现循环的范围出现了问题。
我们把循环改成这样的:
for(i=1;i<=n1;i++)
for(j=1;j<=n2;j++)
然后把if语句的地方改一改:if(s1[i-1]==s2[j-1])
然后就可以AC了。
#include<cstdio>
#include<cstring>
#include<iostream>
#define max(a,b) ((a)>(b)?(a):(b))
#define maxn 1039
using namespace std;
char s1[maxn],s2[maxn];
int f[maxn][maxn];
int n1,n2;
int i,j,k,flag;
int maxx;
int main(){
cin>>s1>>s2;
n1=strlen(s1); n2=strlen(s2);
for(i=0;i<n1;i++)
if(s1[i]<'a')
s1[i]+=32;
for(i=0;i<n2;i++)
if(s2[i]<'a')
s2[i]+=32;
f[0][0]=0;
for(i=1;i<n1;i++)
f[i][0]=f[i-1][0]-2;
for(i=1;i<n2;i++)
f[0][i]=f[0][i-1]-2;
for(i=1;i<=n1;i++)
for(j=1;j<=n2;j++){
flag=0;
if(s1[i-1]==s2[j-1])
flag=1;
f[i][j]=max(f[i-1][j-1]+flag,max(f[i-1][j]-2,f[i][j-1]-2));
}
printf("%d",f[n1][n2]);
return 0;
}