洛谷传送:https://www.luogu.org/problemnew/show/P1140
题意简述:给两个碱基序列A和B和一个碱基匹配的相似度表(权值表),找出两个序列匹配的相似度(最大权值和)。
这一题在洛谷是普及的难度,但困扰了很久,有点难受。
既然是DP,按照套路来分析一波:
一、确定状态
求什么就设什么,确定状态dp[i][j]为 当A中的前i个元素和空字符组成的序列和B中前j个元素和空字符组成的序列可以匹配的最大权值和。
从这里会隐约感到和最长LCS的状态定义是十分相似的。
二、刻画最优子结构
对于序列A和B,采用和LCS一样的分析方式。每次比较最后的两个元素。
有序列A:AGTGATG 和序列B:GTTAG,i 和 j 分别是当前指向的元素位置。
从题目可以看出,可以在A和B的序列中加入空碱基,所以每次最后一位元素比较的时候会出现以下的三种情况:
Ⅰ. A添加空碱基,B不加;
Ⅱ. B添加空碱基,A不加;
Ⅲ. A,B都不添加空碱基。
这样就可以得到这时最优子结构,两个序列的最大权值和就是其去掉最后一个元素的子序列的最大权值和 + 最后一个元素匹配值。
三、状态转移方程
从题目可以看出,可以在A和B的序列中加入空碱基,所以每次最后一位元素比较的时候会出现以下的三种情况:
所以我们可以写出状态转移方程
四、一个递归基
考虑两个序列第一个元素和空碱基的配对问题
for(int i=1;i<=la;i++)
{
dp[i][0]=dp[i-1][0]+table[a[i]]['-'];
}
for(int i=1;i<=lb;i++)
{
dp[0][i]=dp[0][i-1]+table['-'][b[i]];
}
『代码』
//下标从0开始
#include <algorithm>
#include <bits/stdc++.h>
#include <cstring>
#include <iostream>
#include <string>
using namespace std;
int n, m, p = 0;
string a, b;
int dp[205][205];
int main() {
int table_v[5][5] = {
5, -1, -2, -1, -3,
-1, 5, -3, -2, -4,
-2, -3, 5, -2, -2,
-1, -2, -2, 5, -1,
-3, -4, -2, -1, 0
};
int num[200];
string str = "ACGT-";
for (int i = 0; i < 5; i++) {
num[str[i]] = i;
}
cin >> n >> a >> m >> b;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
dp[i + 1][j + 1] = -2e8;
}
}
for (int i = 0; i < n; i++) {
dp[i + 1][0] = dp[i][0] + table_v[num[a[i]]][4];
}
for (int i = 0; i < m; i++) {
dp[0][i + 1] = dp[0][i] + table_v[4][num[b[i]]];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
//a添加空碱基,b不加
dp[i + 1][j + 1] = max(dp[i + 1][j + 1], dp[i + 1][j] + table_v[4][num[b[j]]]);
//b添加空碱基,a不加
dp[i + 1][j + 1] = max(dp[i + 1][j + 1], dp[i][j + 1] + table_v[num[a[i]]][4]);
//a,b都不添加空碱基
dp[i + 1][j + 1] = max(dp[i + 1][j + 1], dp[i][j] + table_v[num[a[i]]][num[b[j]]]);
}
}
cout << dp[n][m];
return 0;
}