[线性DP] 洛谷P1140 相似基因(类LCS)(二维滚动数组)

题目

LP1140

思路

很正常的一道DP,如果做过LCS的话思路会比较清晰。
本题的状态转换比较复杂,所以就导致很难掌握合适的枚举顺序,又因为数据量只有100,所以用记忆化搜索直接递归会比较好写。
一会吃完饭回来有空的化写写递推。

做区间DP做糊涂了。。本题递推直接i,j正枚举就行了。。。一会回来写。。


直接写递推还是很好写,但要是想写滚动数组就得上一个档次,用二维滚动数组。
原因是本题有3个转移,j-1的转移要求枚举顺序必须为正序,i-1的转移对枚举顺序无要求,而i-1,j-1的转移,如果采用正枚举,会导致读到[i]而非[i-1]的数据。
关于二维滚动数组的详细理解和实现方法,看UVa1625
(我就是在写本题的滚动数组的时候意识到uva1625的二维滚动数组是没有必要的。。)


1.状态定义:d(i,j),从两个序列中各取出i,j个元素,最大相似度。
2.初状态:d(0,0)=0, d(i,j) = -INF。
3.答案:d(n,m)。
4.状态转移方程:

d ( i , j ) = m a x { d ( i 1 , j ) + D [ A [ i ] ] [ 0 ] ,   d ( i , j 1 ) + D [ B [ j ] ] [ 0 ] ,   d ( i 1 , j 1 ) + D [ A [ i ] ] [ B [ j ] ] }

(此处初值为-INF而不是0的原因是,最大相似度可能是负数,0还是太大)

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int D[5][5] = {
{0,-3,-4,-2,-1},
{-3,5,-1,-2,-1},
{-4,-1,5,-3,-2},
{-2,-2,-3,5,-2},
{-1,-1,-2,-2,5}
};

const int INF = 1 << 27;
const int maxn = 100 + 10;
char p[maxn], q[maxn]; // 从1开始
int n, m, A[maxn], B[maxn], d[maxn][maxn];

int dp(int i, int j) {
    //printf("%d%d\n", i, j);
    int &ans = d[i][j];
    if (ans != -1) return ans;
    ans = -INF;
    if (i > 0) ans = max(ans, dp(i - 1, j) + D[A[i]][0]);
    if (j > 0) ans = max(ans, dp(i, j - 1) + D[B[j]][0]);
    if (i > 0 && j > 0) ans = max(ans, dp(i - 1, j - 1) + D[A[i]][B[j]]);
    return ans;
}

int main() {
    scanf("%d%s%d%s", &n, p + 1, &m, q + 1);
    char c;
    _rep(i, 1, n) {
        c = p[i];
        if (p[i] == 'A') A[i] = 1;
        else if (p[i] == 'C') A[i] = 2;
        else if (p[i] == 'G') A[i] = 3;
        else if (p[i] == 'T') A[i] = 4;
    }
    _rep(i, 1, m) {
        c = q[i];
        if (q[i] == 'A') B[i] = 1;
        else if (q[i] == 'C') B[i] = 2;
        else if (q[i] == 'G') B[i] = 3;
        else if (q[i] == 'T') B[i] = 4;
    }

    // 递归做法
    memset(d, -1, sizeof(d));
    d[0][0] = 0;
    int ans = dp(n, m);
    printf("%d\n", ans);

    return 0;
}

递推二维滚动数组代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#define _for(i,a,b) for(int i = (a); i<(b); i++)
#define _rep(i,a,b) for(int i = (a); i<=(b); i++)
using namespace std;

const int D[5][5] = {
    { 0,-3,-4,-2,-1 },
{ -3,5,-1,-2,-1 },
{ -4,-1,5,-3,-2 },
{ -2,-2,-3,5,-2 },
{ -1,-1,-2,-2,5 }
};

const int INF = 1 << 27;
const int maxn = 100 + 10;
char p[maxn], q[maxn]; // 从1开始
int n, m, A[maxn], B[maxn], d[2][maxn];

int main() {
    scanf("%d%s%d%s", &n, p + 1, &m, q + 1);
    char c;
    _rep(i, 1, n) {
        c = p[i];
        if (p[i] == 'A') A[i] = 1;
        else if (p[i] == 'C') A[i] = 2;
        else if (p[i] == 'G') A[i] = 3;
        else if (p[i] == 'T') A[i] = 4;
    }
    _rep(i, 1, m) {
        c = q[i];
        if (q[i] == 'A') B[i] = 1;
        else if (q[i] == 'C') B[i] = 2;
        else if (q[i] == 'G') B[i] = 3;
        else if (q[i] == 'T') B[i] = 4;
    }

    // 递推做法
    int t = 0;
    _rep(i, 0, n) {
        _rep(j, 0, m) {
            if (i == 0 && j == 0) continue;
            int &ans = d[t][j];
            ans = -INF;
            if (i > 0) ans = max(ans, d[t^1][j] + D[A[i]][0]);
            if (j > 0) ans = max(ans, d[t][j - 1] + D[B[j]][0]);
            if (i > 0 && j > 0) ans = max(ans, d[t^1][j - 1] + D[A[i]][B[j]]);
        }
        t ^= 1;
    }

    printf("%d\n", d[t^1][m]);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/icecab/article/details/80958267