zoukankan      html  css  js  c++  java
  • POJ3280 Cheapest Palindrome 动态规划求回文串

    前面自己写的动态方程转移出现了问题,还是没有考虑周全.

    AC代码:

    #include <cstdlib>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    /*
    题意:给定了一个字符串,问这个字符串,这个字符串是由a,b,c,...,a+N-1的字符集组成的
         现在问使用使得该串成为一个回文串的最少花费是多少. 花费的定义如下:
         对于每一个字符,给出添加这个字符和删除这个字符分别需要花费多少. 添加和删除
         可以再任何位置进行.
    
    解法:前面的写法是错误的,因为状态中的dp[i][j]表示从i到j只能够去删除和改变字符,
         并没有涉及到添加字符进去的操作,也就是说dp[i][j]取得最优值的时候长度可能
         是大于j-i+1的. 
         
         说明前面开设的状态理解有问题, 也就导致转移方程不能够很好的解决问题 
         重新定义dp[i][j]表示可能含有[i,j]子串的回文串的最少开销是多少.
         那么就有动态方程:
         dp[i][j] = min(dp[i+1][j]+cost[i], dp[i][j-1]+cost[j]); 
         上述方程生动的描述了对边界的两个字符的选择过程,cost[i]本身就是经过选择
         而来的值,其表示是删除一个i节点还是在右边增加一个i节点. 加上后面的一部分
         合起来就是对左右两边字符的考虑. 
         if (str[i] == str[j])的话,那么就还有一种选择了
         dp[i][j] = min(dp[i][j], dp[i-1][j-1]);
         
    */
    
    int N, M, dp[2005][2005];
    int cost[30];
    char str[2005];
    
    int DP() {
        for (int i = 1; i < M; ++i) { // 枚举区间左右端点的差值 
            for (int j = 1; j+i <= M; ++j) {
                int a = j, b = j + i, ll = str[a]-'a', rr = str[b]-'a';
                dp[a][b] = min(dp[a+1][b] + cost[ll], dp[a][b-1] + cost[rr]);
                if (ll == rr)
                    dp[a][b] = min(dp[a][b], dp[a+1][b-1]); 
                // 如果端点差值为1的话,dp[a+1][b-1]会使得区间无意义,当时这个无意义的值刚好为0
            }
        }
        return dp[1][M];
    }
    
    int main() {
        char s[5];
        int a, b;
        while (scanf("%d %d", &N, &M) == 2) {
            scanf("%s", str+1);
            for (int i = 1; i <= N; ++i) {
                scanf("%s %d %d", s, &a, &b);
                cost[s[0]-'a'] = min(a, b);  
                // 这里直接求出这个最小值,因为对每一个点有两种选择,一是删去,而是添加一个 
            }
            printf("%d\n", DP());
        }
        return 0;
    }

    WA代码:

    View Code
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <algorithm>
    #define INF 0x3f3f3f3f
    using namespace std;
    
    /*
    题意:给定了一个字符串,问这个字符串,这个字符串是由a,b,c,...,a+N-1的字符集组成的
         现在问使用使得该串成为一个回文串的最少花费是多少. 花费的定义如下:
         对于每一个字符,给出添加这个字符和删除这个字符分别需要花费多少. 添加和删除
         可以再任何位置进行.
    
    解法:设定状态dp[i][j]表示[i,j]这段区间为回文串的最少花费是多少. 那么有dp方程:
         dp[i][j] = min(dp[i-1][j-1]+X, dp[i-1][j]+X, dp[i][j-1]+X), 第一个表示左右
         边界都要,第二个和第三个分别表示左边和右边的一个删除 
         
         最后的回文串一定不会是两端都添加字符, 否则同时在两端删除新添加的字符一定也
         是最优解 
    */
    
    int N, M, dp[4005][4005];
    char str[4005];
    
    struct Node {
        int afee, dfee;
    }e[30];
    
    int DP() {
        int lim = M << 1; // 最多可以再添加一个M长的字符串在后面 
        memset(dp, 0x3f, sizeof (dp));// 保证非法状态dp值为无穷大 
        for (int i = 1; i <= lim; ++i) { // 初始化只有一个字符的串 
            dp[i][i] = 0; // 该位置已有字符,最好的办法就是什么都不做,形成一个单字符回文串
        }
        for (int i = 1; i < lim; ++i) { // 初始化只有两个字符的串 
            if (str[i] == str[i+1]) dp[i][i+1] = 0;
            else {
                int Min = INF;
                Min = min(Min, e[str[i]-'a'].dfee);
                Min = min(Min, e[str[i+1]-'a'].dfee);
                dp[i][i+1] = Min;
            }
        }
        // 根据dp[i][j]的含义,显然i <= j的情况下,dp值才有意义
        for (int i = 2; i < lim; ++i) { // 枚举区间左右端点的差值
            for (int j = 1; j + i <= lim; ++j) {
                int a = j, b = j + i; // a, b 分别表示左右边界
                int Min = INF, ll = str[a]-'a', rr = str[b]-'a';
                Min = min(Min, dp[a+1][b] + e[ll].dfee); // 删除a号位置
                Min = min(Min, dp[a][b-1] + e[rr].dfee); // 删除b号位置
                if (ll == rr) {
                    Min = min(Min, dp[a+1][b-1]);
                }
                else {
                    Min = min(Min, dp[a+1][b-1] + e[ll].dfee + e[rr].dfee); // 删除左右两个
                    Min = min(Min, dp[a+1][b-1] + e[rr].dfee + e[ll].afee); // 将右边改为左边字符 
                    Min = min(Min, dp[a+1][b-1] + e[ll].dfee + e[rr].afee); // 将左边改为右边字符 
                }
                dp[a][b] = Min;
            }
        }
        return dp[1][lim];
    }
    
    void srev(char *s) {
        int len = strlen(s);
        for (int i = 0, j = len-1; i < j; ++i, --j) {
            char t = s[i];
            s[i] = s[j];
            s[j] = t;    
        }
    }
    
    int main() {
        char s[5];
        int a, b, ret;
        while (scanf("%d %d", &N, &M) == 2) {
            ret = INF;
            memset(str, 'a'+26, sizeof (str));
            // 全部赋值为26,这样就假设后面补了一个字母,这个字母的代价添加和删除都是0 
            scanf("%s", str+1); // 从第一位开始录入
            str[M+1] = 'a'+26; // 人为把'\0'赋值为26
            for (int i = 0; i < N; ++i) {
                scanf("%s %d %d", s, &a, &b); 
                e[s[0]-'a'].afee = a;
                e[s[0]-'a'].dfee = b; // 将添加和删除字符的代价记录起来
            }
            ret = min(ret, DP()); // 都假设从后面添加字符
            str[M+1] = '\0';
            srev(str+1);
            str[M+1] = 'a'+26;
            ret = min(ret, DP()); // 通过翻转也就等同与从前面添加字符 
            printf("%d\n", ret);
        }
        return 0;
    }
  • 相关阅读:
    python2和python3中TestSuite().addTest的区别
    python2和python3中range的区别
    WebDriverAgent安装
    Charles IOS https抓包
    Python和 pytest的异常处理
    Python中yaml和json文件的读取和应用
    Python 获取当前文件所在路径
    自建包函数调用
    python的logging,将log保存到文件
    泛型
  • 原文地址:https://www.cnblogs.com/Lyush/p/2859702.html
Copyright © 2011-2022 走看看