Palindrome DP

 

Palindrome

Time Limit : 6000/3000ms (Java/Other)   Memory Limit : 131072/65536K (Java/Other)
Total Submission(s) : 57   Accepted Submission(s) : 18
Problem Description
A palindrome is a symmetrical string, that is, a string read identically from left to right as well as from right to left. You are to write a program which, given a string, determines the minimal number of characters to be inserted into the string in order to obtain a palindrome. 
As an example, by inserting 2 characters, the string "Ab3bd" can be transformed into a palindrome ("dAb3bAd" or "Adb3bdA"). However, inserting fewer than 2 characters does not produce a palindrome. 
 

 

Input
Your program is to read from standard input. The first line contains one integer: the length of the input string N, 3 <= N <= 5000. The second line contains one string with length N. The string is formed from uppercase letters from 'A' to 'Z', lowercase letters from 'a' to 'z' and digits from '0' to '9'. Uppercase and lowercase letters are to be considered distinct.
 

 

Output
Your program is to write to standard output. The first line contains one integer, which is the desired minimal number.
 

 

Sample Input
5 Ab3bd
 

 

Sample Output
2
 

 

题意:给定一个字符串,至少插入几个字符才能使它变成回文串。

题解:

1.求出自身与其逆序列的最长公共子序列长度s,总长度l-s就是要求解。

代码如下:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 
 6 #define N 5003
 7 int dp[N][N];
 8 char a[N],b[N];
 9 
10 int max(int a,int b)
11 {
12     return a>b?a:b;
13 }
14 
15 int main()
16 {
17     int n,i,j;
18     scanf("%d",&n);
19     scanf("%s",a+1);
20     for(i=1; i<=n; i++)
21         b[i]=a[n-i+1];
22     for(i=1; i<=n; i++)
23         dp[i][0]=dp[0][i]=0;
24     for(i=1; i<=n; i++)
25         for(j=1; j<=n; j++)
26         {
27             if(a[i]==b[j])
28                 dp[i][j]=dp[i-1][j-1]+1;
29             else
30                 dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
31         }
32     if(dp[n][n]==0)  dp[n][n]=1;
33     printf("%d\n",n-dp[n][n]);
34 
35 }

 

 

2.网上有人总结了另外两种方法:

(1):用dp直接求;

 

s1 为原字符串, s2 由 s1 从右至左复制得到。

dp [ i ] [ j ] 表示让 s1 [ i ] 与 s2 [ j ] 左边的字符串完全相等最少需要插入的字符个数。
1.当 s1 [ i ]  != s2 [ j ] 时, 假设已知:

substr1 : ·······s1 [ i - 1 ]

substr2 : ·······s2 [ j ]

substr1 == substr2   现在要求 dp [ i ] [ j ], 那么必须在 s2 [ j ] 的右边添加一个等于 s1 [ i ] 的字符, 即 substr1 : ·······s1 [i-1], s1 [ i ] substr2 : ·······s2 [ j ],   s1’[ i ] 那么 dp [ i ] [ j ] = dp [ i - 1] [ j ] + 1
同理 : dp [ i ] [ j ] = dp [ i ] [ j - 1] + 1;
那么 dp [ i ] [ j ] = min ( dp [ i - 1 ] [ j ] + 1, dp [ i ] [ j - 1 ] + 1 );


2.当 s1 [ i ] == s2 [ j ] 时, 假设已知: substr1 : ········s1 [ i - 1 ]  substr2 : ········s2 [ j - 1 ] substr1 == substr2 dp [ i ] [ j ] = dp [ i - 1 ] [ j - 1 ]   注意: 1:由于s2 是由 s1 从右至左复制得到的, 根据对称性。答案应该为 dp [ len ] [ len ] / 2. ( 我试过只求到 dp [ len / 2 ] [ len / 2 ],但是某些细节没考虑到,老是 wrong, 放弃了。有知道的麻烦回复下。) 2.初始化要注意,dp [ i ] [ 0 ] = dp [ 0 ] [ i ] = i。道理很简单,假如要是 s1 的第 i 个字符与 s2 的第 0 个字符的左边完全一样,需要在 s2 的左边添加 s1[1], s1[2] ··· s1 [ i ]. 

 

 

#include <iostream>   

using namespace std;  

#define N 5005    

short dp[N][N];  

char s1[N], s2[N];  

 int min ( int a, int b )  

{  

    return a < b ? a : b;  

}  

 int main()  

{  

    int len, i, j;  

    while ( scanf("%d",&len) != EOF )  

    {  

        scanf("%s",s1+1);  

        i = 1; j = len;  

        while ( i <= len && j >= 1  )  

        {  

            s2[i] = s1[j];  

            i++; j--;  

        }  

          

        for ( i = 0; i <= len; i++ )  

            dp[i][0] = dp[0][i] = i;  

          

        for ( i = 1; i <= len; i++ )  

        {  

            for ( j = 1; j <= len; j++ )  

            {  

                if ( s1[i] == s2[j] )  

                   dp[i][j] = dp[i-1][j-1];  

                else          

                    dp[i][j] = min (  dp[i-1][j] + 1, dp[i][j-1] + 1 );  

            }  

        }  

        printf("%d\n", dp[len][len] / 2 );  

    }  

    return 0;  

}  

 

(2):用滚动数组;

 

设s[1]..s[n]表示字符串1至n位,i为左游标,j为右游标 ,则 i 从 len 递减,j 从 i+1 开始递增。这样做的效果是,开始令最右边的部分变为回文串,而后回文串逐渐向左边大。 dp[i][j]表示i和j之间至少需要插入多少个字符才能构成一个局部的回文串, 初始置全0 ,我们最终需要得到的值是dp[1][n]。则: if ( s [ i ] == s [ j ] )                                    //如果两个游标所指字符相同,向中间缩小范围     dp [ i ] [ j ] = dp [ i + 1 ] [ j - 1 ]; else      dp [ i ] [ j ] = 1 +  min( dp [ i + 1 ] [ j ] , dp [ i ] [ j - 1 ])

 

下面的代码用滚动数组优化了内存。

 #include <iostream>  
 using namespace std;    
#define N 5005   

short dp[2][N];  

char s[N];  

  

int min ( int a, int b )  

{  

    return a < b ? a : b;  

}  

  

int main()  

{  

    int len,i,j;  

    //freopen("a.txt","r",stdin);   

    while ( scanf("%d",&len) != EOF )  

    {  

        scanf("%s",s+1);  

        memset(dp,0,sizeof(dp));  

        for ( i = len; i >= 1; i-- )  

        {  

            for ( j = i+1; j <= len; j++ )  

            {  

                if ( s[i] == s[j] )  

                    dp[i%2][j] = dp[(i+1)%2][j-1];  

                else  

                    dp[i%2][j] = min ( dp[i%2][j-1], dp[(i+1)%2][j] ) + 1;  

            }  

        }  

        printf ( "%d\n",dp[1][len]);  

    }  

    return 0;  

}  

posted on 2013-09-12 16:15  平心静气  阅读(177)  评论(0)    收藏  举报

导航