填坑行动2-字符串DP


这是填坑行动的第二篇-字符串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} fi1,j,fi,j1,fi1,j1之类的来进行类推,有些时候我们甚至会用到 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} fi1,fi2之类的来进行递推。
三、背包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=fi1,j1+1
当然,我们也可以推出另外两个方程转移式:
f i , j = f i − 1 , j − 2 f_{i,j}=f_{i-1,j}-2 fi,j=fi1,j2
f i , j = f i , j − 1 − 2 f_{i,j}=f_{i,j-1}-2 fi,j=fi,j12
综合可得:
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=maxfi,j+1fi1,j2fi,j12s1i=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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值