首页 > 技术文章 > hdu 1513 && 1159 poj Palindrome (dp, 滚动数组, LCS)

bfshm 2014-04-17 20:46 原文

题目

以前做过的一道题, 今天又加了一种方法 整理了一下。。。。。

题意:给出一个字符串,问要将这个字符串变成回文串要添加最少几个字符。

 

方法一:

将该字符串与其反转求一次LCS,然后所求就是n减去 最长公共子串的长度。

额,,这个思路还是不是很好想。

LCS:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 const int maxn = 5000+10;
 6 char s1[maxn], s2[maxn];
 7 int d[2][maxn], n;
 8 int mmin(int a,int b)
 9 {
10     return a>b?b:a;
11 }
12 int mmax(int a, int b)
13 {
14     return a<b?b:a;
15 }
16 void Lcs()
17 {
18     int i, j;
19     for(i = 1; i <= n; i++)
20         for(j = 1; j <= n; j++)
21             if(s1[i] == s2[j])
22                 d[i%2][j] = d[(i-1)%2][j-1] + 1;
23             else
24                 d[i%2][j] = mmax(d[(i-1)%2][j], d[(i%2)][j-1]);
25 }
26 int main()
27 {
28     int i;
29     while(cin>>n)
30     {
31         memset(d, 0, sizeof(d));
32         for(i=1; i<=n; i++)
33             cin>>s1[i];
34         for(i = n; i >=1; i--)
35             s2[i] = s1[n-i+1];
36         Lcs();
37         cout<<n-d[n%2][n]<<endl;
38     }
39     return 0;
40 }

 

方法二:

这个是discuss里的方法。

设ch[1]..ch[n]表示字符串1至n位,i为左游标,j为右游标 ,则i从n递减,j从i开始递增。
min[i][j]表示i和j之间至少需要插入多少个字符才能对称,初始置全0 ,我们最终需要得到的值是min[1][n].
则
if(ch[i]==ch[j])                                    //如果两个游标所指字符相同,向中间缩小范围
    min[i][j]=min[i+1][j-1];
else
     min[i][j] = 1 +  (min[i+1][j]和min[i][j-1]中的较小值);     //如果不同,典型的状态转换方程

 下面这个代码 是用的short int d[5000][5000]

在poj 可以水过,但是在hdu还超内存,,所有需要用滚动数组来 节省内存。

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 
 6 int mmin(int a,int b)
 7 {
 8     return a>b?b:a;
 9 }
10 short int d[5010][5010];
11 int main()
12 {
13     int n,i,j;
14     char s[5010];
15     memset(d,0,sizeof(d));
16     cin>>n;
17     for(i=1; i<=n; i++)
18     cin>>s[i];
19     for(i=n; i>=1; i--)
20     for(j=i+1; j<=n; j++)
21     if(s[i]==s[j])
22     d[i][j]=d[i+1][j-1];
23     else
24     d[i][j]=mmin(d[i+1][j],d[i][j-1])+1;
25 
26     cout<<d[1][n]<<endl;
27     return 0;
28 }

 

因为d[i][j] 算的时候只是与 d[i+1][j] 和 d[i][j+1]有关,所有可以开一个d[2][5000]的数组。

滚动数组节省内存:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<cstdio>
 4 using namespace std;
 5 
 6 int mmin(int a,int b)
 7 {
 8     return a>b?b:a;
 9 }
10 int d[2][5010];
11 int main()
12 {
13     int n,i,j;
14     char s[5010];
15     while(cin>>n)
16     {
17         memset(d, 0, sizeof(d));
18         for(i=1; i<=n; i++)
19             cin>>s[i];
20 
21         for(i=n; i>=1; i--)
22             for(j=i+1; j<=n; j++)
23                 if(s[i]==s[j])
24                     d[i%2][j]=d[(i+1)%2][j-1];
25                 else
26                     d[i%2][j]=mmin(d[(i+1)%2][j],d[i%2][j-1])+1;
27 
28         cout<<d[1][n]<<endl;
29     }
30     return 0;
31 }

 

贴一下别人博客里的滚动数组的介绍:

 

滚动数组 举个简单的例子:
int i,d[100];
d[0]=1;d[1]=1;
for(i=2;i<100;i++)
d[i]=d[i-1]+d[i-2];
printf("%d",d[99]);
上面这个循环d[i]只需要解集中的前2个解d[i-1]和d[i-2];
为了节约空间用滚动数组的方法


int d[3];
d[0]=1;d[1]=1;
for(i=2;i<100;i++)
d[i%3]=d[(i-1)%3]+d[(i-2)%3];
printf("%d",d[99%3]);


注意上面的运算,我们只留了最近的3个解,数组好象在“滚动?一样,所以叫滚动数组


对于二维数组也可以用这种方法 例如:
int i,j,d[100][100];
for(i=1;i<100;i++)
    for(j=0;j<100;j++)
        d[i][j]=d[i-1][j]+d[i][j-1];


上?的d[i][j]忪便赖于d[i-1][j],d[i][j-1];
迿用滚动数组
int i,,j,d[2][100];
for(i=1;i<100;i++)
    for(j=0;j<100;j++)
        d[i%2][j]=d[(i-1)%2][j]+d[i%2][j-1];


滚动数组实际是一种节约空间的办法,时间上没什么优势,多用于DP中,举个例子先: 
一个DP,平常如果需要1000×1000的空间,其实根据DP的特点,能以2×1000的空间解决问题,并且通过滚动,获得和1000×1000一样的效果。

推荐阅读