题意是求给一个字符串算出其中有多少个回文子串(每个回文子串必须是连续的),关于这题由于数据范围才5000,所以可以用O(N*N)的动态规划去做或者是用暴力的搜索,每次枚举回文串的对称中点复杂度不超过O(N*N),但是用dp做就很蛋疼了,因为内存限制存不了所有的状态,所以得用滚动数组去存,下面将给出几种不同实现的代码。如果暴力枚举中点方法确实很简单,但我还是建议,写一下dp,/可以锻炼一下代码能了,或者说是扩展一下思维吧 这题有成型的算法是MANACHER算法。。
***********************************************************
强烈建议看一下这题,也是回文串不过就只能dp了点击打开链接,dp的状态转移方程十分相似,但难度不一样
*************************************************************
我的动态规划代码,因为这题和我前不久做过的一题很像,所以就套用了一下思路,写了一个dp式 dp[i][j]=dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1] ,另外如果从i到j的子串也是回文串则dp[i][j]+=1;dp[i][j] 表示从i到j内有多少个满足的回文子串。举例,如ch[10000]=asa则dp[0][1] 的值是2,有a和s 2个回文子串 又dp[1][1]=1,dp[1][2]=2且 ch[0]=ch[2]同时有ch[1]到ch[1]也是回文子串 所以 d[0][2]=dp[0][1]+dp[1][2]-dp[1][1]+1=4
强烈建议看一下这题,也是算回文串不过就只能dp了点击打开链接
有了这个递推式要是写记忆化搜索是很快的,于是我就有了如下代码(别急着ctrl+c 代码不会AC的)
#include <cstdio> #include<cstring> #include <bitset> using namespace std; int dp[5010][5010],len; char ch[5010]; bitset<5010> vis[5010];//用这个bitset做标记实在是太好了,很省内存 int memdp(int i,int j)//这个是记忆化搜索的代码 { if(i>j) return 0; if(i==j) return vis[i][j]=dp[i][j]=1; if(dp[i][j])return dp[i][j]; dp[i][j]=memdp(i+1,j)+memdp(i,j-1)-memdp(i+1,j-1); if(ch[i]==ch[j]) { if(i+1==j){vis[i][j]=1;return dp[i][j]=3;}//当i和j相邻时可以直接返回结果 if(vis[i+1][j-1])//若从i+1到j-1 是回文串则说明从i到j也是一个回文串了,因为已经判了ch[i]==ch[j] { vis[i][j]=1;//标记从i到j是回文串 return ++dp[i][j];//返回 } } return dp[i][j]; } int main() { while(~scanf("%s",ch)) { memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); len=strlen(ch); printf("%d\n",memdp(0,len-1)); } return 0; }
于是就有了以下的代码,实际上我还做了另一个优化,没有了标记数组,就是上面的代码中的vis数组,我把dp数组的个位数字给用成标记了,把dp数组的值整除10,才是答案,
想想这下总该能A了吧,其中的j%2就是滚动数组了,因为这样后dp[i][j]的实际位置就不断在dp[i][0]和dp[i][1]之间滚来滚去,滚来滚去了。。。
#include<cstdio> #include<cstring> char ch[5012]; int dp[5010][2]; int memdp(int i,int j)//记忆化搜索运用了滚动数组,就是把j每次用2取模,就可以了 { if(i>j) return 0; if(i==j) return dp[i][j%2]=11; dp[i][j%2]=memdp(i+1,j)/10+memdp(i,j-1)/10-memdp(i+1,j-1)/10;//因为个位是标记故不能直接相加,得除以10才行 dp[i][j%2]*=10;//乘以10,即把个位标记变成0,恢复dp这个数的意义 if(ch[i]==ch[j]) { if(i+1==j){return dp[i][j%2]=31;} if(dp[i+1][((j-1)+2)%2]%10)//取出标记进行判定 { dp[i][j%2]++; return dp[i][j%2]+=10; } } return dp[i][j%2]; } int main() { while(~scanf("%s",ch)) { memset(dp,0,sizeof(dp)); int len=strlen(ch); printf("%d\n",memdp(0,len-1)/10); } return 0; }
#include<cstdio> #include<cstring> char ch[5012]; int dp[5010][2]; int main() { while(~scanf("%s",ch)) { memset(dp,0,sizeof(dp)); dp[0][0]=11; int len=strlen(ch); for(int j=1;j<len;j++)//用记忆化搜索改成的非递归形式 { int jm2=j%2;//这个是为了不TLE用的 int jj2=!jm2;//这个也是为了省时间的,其实这个是(j-1)%2 for(int i=j;i>=0;i--) { if(i==j) dp[i][jm2]=11; else { dp[i][jm2]=dp[i+1][jm2]/10+dp[i][jj2]/10-dp[i+1][jj2]/10; dp[i][jm2]*=10; if(ch[i]==ch[j]) { if(i+1==j) dp[i][jm2]=31; else if(dp[i+1][jj2]%10) { dp[i][jm2]++; dp[i][jm2]+=10; } } } } } printf("%d\n",dp[0][(len-1)%2]/10); } return 0; }功夫不负有心人终于AC了,别提心情有多爽啦,哈哈。。。
这个过程可以说是非常地蛋疼啊,但是我感觉确实锻炼了一点代码能力,和优化技巧,如其中的记忆化搜素,写起来很简单,但因为是递归所以会慢一点,所以得会改成非递归的算法,而且滚动数组可以节省大量的内存,这个必须会,另外那个把标记数组和别的数组压缩合在一起的方法其实很好的,有时候很方便,另外stl里面的bitset也是很好用的,十分节省内存,相信你看完这篇博客,还是能有所收获的
下面贴一个我同学的动态规划的代码,他的比我的要快,但比我的要难懂一点,他没有把我的dp数组保存下来,而是直接累加在ans的全局变量里面了,
思路:用f(i,j)代表str[i....j]是不是回文串,容易得出递推式f(i,j)=f(i+1,j-1) && str[i]==str[j],然后将所有的f(i,j)加起来就是答案了。(注意f(i,i)=1和i+1>j-1的情况)
但是这个题目开5000*5000的数组会MLE。于是注意到每次f(i,j)只需要用到f(i+1,j-1),即它的前两层的状态,所以只要开一个5000*3的滚动数组就可以了。
#include<cstdio> #include<cstring> #include<iostream> #define MAXN 5005 using namespace std; int f[MAXN][3],n,ans; char s[MAXN]; void getf() { n=strlen(s),ans=0; for(int k=0;k<n;++k) for(int i=0;i+k<n;++i) { int j=i+k,now=k%3,pre2=(now-2+3)%3; f[i][now]=(k==0 || (k==1 && s[i]==s[j]) || (i+1<=j-1 && s[i]==s[j] && f[i+1][pre2])); ans+=f[i][now]; } } int main() { while(~scanf("%s",s)) { getf(); cout<<ans<<endl; } }
#include<cstdio> #include<iostream> #include<cstring> #define maxn 5009 using namespace std; char str[maxn]; int main() { int i,j,len,ans,temp; while(scanf("%s",&str)!=EOF) { ans=len=strlen(str); for(temp=0;temp<len;temp++) { i=temp; j=temp-1; while(str[i]==str[j] && i<len && j>=0) { ans++; i++;j--; } i=temp+1; j=temp-1; while(str[i]==str[j] && i<len && j>=0) { ans++; i++; j--; } } cout<<ans<<endl; } return 0; }