POJ 1159 Palindrome 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;
}


方法二:
求最大公共子序列。
已知s1, s2, 同样的 s2 是由s1从右至左复制得到的。
s1[1], s1[2], ·······s1[i] ····················
s2[1], s2[2], ·······················s2[j],····
为了使 s1 == s2, 我们每当碰到 s1 [ i ] != s2 [ j ], 都在 s1 中加入一个 s2' [ j ], 在s2 中加入一个字符 s1' [ i ], 变成:
s1[1], s1[2], ·······s1 [ i ] ···············s2'[ j ]·····
s2[1], s2[2], ·······s1'[ i ]················s2 [ j ]·····
最终一定可以得到 s1 == s2,即回文串。 此时在 s1 中加入的字符个数恰好等于 s1 [ i ] != s2 [ j ] 的对数。即: 加入字符个数 = len - 公共子序列长度,因此只需要求最长公共子序列。
#include <iostream>
using namespace std;

#define N 5005
short dp[N][N];
char s1[N], s2[N];

int max ( 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",s1+1);
		i = 1; j = len;
		while ( i <= len && j >= 1  )
		{
			s2[i] = s1[j];
			i++; j--;
		}
		
		for ( i = 1; i <= len; i++ )
			dp[i][0] = dp[0][i] = 0;
		
		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] + 1;
				else
					dp[i][j] = max ( dp[i-1][j], dp[i][j-1] );
			}
		}
	
		if ( dp[len][len] == 0 ) dp[len][len] = 1;
		printf("%d\n", len - dp[len][len] );
	}
	return 0;
}



方法三:
设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;
}





你可能感兴趣的:(POJ 1159 Palindrome DP)