poj 3356 AGTC(线性dp)

题目链接:http://poj.org/problem?id=3356

思路分析:题目为经典的编辑距离问题,其实质为动态规划问题;

编辑距离问题定义:给定一个字符串source,可以对其进行复制,替换,删除,增加操作,另外根据具体情况已经规定了每种操作的cost,现在要求求出一个操作序列,使其变为一个给定的字符串dest,并且该操作序列的cost的和最小(在该题目中复制开销为0,其他开销为1);

 

该问题为动态规划问题,先对该问题进行分析:

1)发掘最优子结构:

假设源字符串为S[0, 1, 2,..,n-1],其长度为n,目标字符串为D[0, 1, 2, ...., m-1],长度为m,则该问题转换为求一个cost最小的操作序列将

S[0,1,2,...,n-1]转换为D[0, 1, 2, ...., m-1];先考虑如何将S[0]转换为D[0];为了将S[0]转换为D[0],我们先选择一个操作,假设该操作是产生最

优的操作序列的第一个操作,这样我们就能将S[0]转换为D[0],则原来的问题在该操作后产生了一个子问题(在不同的动态规划问题中可能会产

生不同的子问题个数),即求一个cost最小的操作序列将S[1, 2, ... , n-1]转换为D[1, 2, ..., m-1],我们可以证明该子问题的最优解可以构造出原

来问题的最优解,这样我们就发现了该问题的最优子结构;另外,我们还需要考虑第一步的选择有多少种(不同动态规划问题的选择的可能性的

种类数目不同),即将S[0]转换为D[0]可以有多少种操作方法,明显在这个问题中有4种;

 

2)重叠子问题

如果该问题的递归算法反复地求解子问题,那么我们就称该最优化问题具有重叠子问题性质;在分治算法中,递归算法会生成全新的子问题,

子问题与子问题之间是无关的;而动态规划算法不同,如求斐波那契数列的递归算法中求数列f[n]需要求解f[n-1]与f[n-2],而f[n-1]=f[n-2]+f[n-3],

可以看到,在求斐波那契数列的递归算法中f[n-2]被求解多次,则求具有重叠子问题的结构,但是求斐波那契数列算法不是动态规划算法,因为其

不具有最优子结构,这里提出该问题是让大家对重叠子问题有一个具体的认识;现在,我们可以明显看到,在求解编辑距离问题中,我们做出一次

选择,就会产生一个子问题,因为在每一步中我们可能会做出多个选择,所以会求解多个相同的子问题,如在第一步中,我们可以选择复制(如果

S[0]==D[0]),删除,替换,增加操作,每个选择的操作就会产生相同的子问题,即求解S[1,2, .., m]转换D[1, 2, ..., n]的最小的cost的操作序列;

所以该问题具有重叠子问题;

 

3)解法:

对于该问题,我们刻画该问题的问题空间:假设dp[i][j]表示从字符串S[i, i+1, i+2, ...., m-1]转换为字符串D[j, j+1, j+2, ...., n-1]的操作序列的最小的cost和,

则原问题为求dp[0][0];

对于特殊的情况:

dp[i, n] = m-i,表示使S[i, i+1, ...,m-1]转换为空字符串,只能删除m-i个字符,所以最小的cost和为m-i;

dp[m, j] = n-j,表示使空字符串转换为D[j, j+1, ..., n-1],则只能增加n-j个字符,所以最小的cost和为n-j;

关于选择的可能:

<1>复制:dp[i, j] = dp[i+1, j+1], 如果S[i]==D[j],可以使用复制操作;

<2>删除:dp[i, j] = dp[i+1, j] + 1,删除操作cost为1,删除S[i],子问题为dp[i+1, j];

<3>替换:dp[i, j] = dp[i+1, j+1] + 1,替换操作只能在S[i]与D[j]不相等时才能选择;

<4>增加:dp[i, j] = dp[i, j+1] + 1,同样的,增加操作cost为1;

则该问题的动态规划方程已经给出,可以求解该问题;

 

代码如下:

#include <cstdio>
#include <iostream>
using namespace std;

const int MAX_N = 1000 + 10;
char source[MAX_N], dest[MAX_N];
int dp[MAX_N][MAX_N];

inline int Min(int a, int b) { return a > b ? b : a; }

int main()
{
    int s_len, d_len;

    while(scanf("%d %s", &s_len, source) != EOF)
    {
        scanf("%d %s", &d_len, dest);

        memset(dp, 0, sizeof(dp));
        for (int i = 0; i <= d_len; ++i)
            dp[s_len][i] = d_len - i;
        for (int i = 0; i <= s_len; ++i)
            dp[i][d_len] = s_len - i;
        for (int i = s_len - 1; i >= 0; --i)
        {
            for (int j = d_len - 1; j >= 0; --j)
            {
                int min_cost = 100000;

                min_cost = Min(min_cost, dp[i + 1][j] + 1);
                min_cost = Min(min_cost, dp[i][j + 1] + 1);
                if (source[i] == dest[j])
                    min_cost = Min(min_cost, dp[i + 1][j + 1]);
                else
                    min_cost = Min(min_cost, dp[i + 1][j + 1] + 1);
                dp[i][j] = min_cost;
            }
        }
        printf("%d\n", dp[0][0]);
    }

    return 0;
}

你可能感兴趣的:(poj)