POJ 1159 Palindrome

  POJ 1159

  题目大意:给定一个长度为n的字符串,求至少添加多少个字符能使得它变成回文串

  解题思路:求原串的逆串(abcdd的逆串为ddcba),然后求原串与逆串的最长公共子序列,即为c,则n-c即为最少添加的字符(不懂如何证明,只能大脑大概过明白)

       求两个字符串a,b的最长公共子序列:

         定义状态dp[i][j]表示a的前i个字符和b的前j个字符的最长公共子序列,那么dp[len1][len2]即为所求结果.

         初始状态: dp[i][0] = 0, dp[0][j] = 0, 对所有 i <= len1, j <= len2;

         状态转移方程: a[i] == b[j]时, dp[i][j] = dp[i-1][j-1]+1

                 a[i]  != b[j]时, dp[i][j] = MAX(dp[i-1][j], dp[i][j-1]);

         更加具体的状态转移方程推导请参考: HDU 1159题解

       另外由于此题n可以达到5000,因此开一个dp[5005][5005]的数组会超出内存,

       因此需采用一种叫做滚动数组的方式来节省空间。

       看状态转移方程,任何dp[i][j]的推导只使用到上一行的数组的状态,

       因此实际上开一个dp[2][5005],并把另一个当做当前需要更新的行的上一个状态即可。

       滚动数组有两种实现方式:

        一是将行进行取模: 如 dp[i%2][j] = dp[(i-1)%2][j]+1; (ai == bi时)

        二是定义一个变量e = 0,  每更新一行,就变换e,使e = 1 - e,即dp[e][j] = dp[1-e][j]+1; (ai == bj时)

 

/* POJ 1159 Palindrome --- dp+滚动数组 */
#include <cstdio>
#include <cstring>

const int N = 5005;
int dp[2][N];
char str1[N], str2[N];
int n;

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

int main()
{
#ifdef _LOCAL
    freopen("D:\\input.txt", "r", stdin);
#endif
    int n;
    //字符串长度为n
    while (scanf("%d", &n) == 1){
        scanf("%s", str1 + 1);
        //求逆串 
        for (int i = n; i > 0; --i){
            str2[n - i + 1] = str1[i];
        }
        int e = 0;
        memset(dp, 0, sizeof dp);

        //dp[i][j]表示子串的最大长度
        for (int i = 1; i <= n; ++i){
            e = 1 - e; //需要更新的行 即当前行 对应n*n中的 i
            for (int j = 1; j <= n; ++j){
                if (str1[i] == str2[j]){
                    dp[e][j] = dp[1 - e][j - 1] + 1; //1-e对应i-1
                }
                else{
                    dp[e][j] = MAX(dp[1 - e][j], dp[e][j - 1]);
                }
            }
        }
        printf("%d\n", n - dp[e][n]);
    }

    return 0;
}
View Code

 

posted @ 2016-03-22 00:49  tan90丶  阅读(235)  评论(0编辑  收藏  举报