Posted on 2010-10-13 11:00
李東亮 閱讀(1632)
評(píng)論(0) 編輯 收藏 引用
ZOJ 2744 Palindromes
這道題要求出一個(gè)長(zhǎng)度不大于5000的字符串所有子串中回文字符串的個(gè)數(shù)。可以用暴力brute-force方法解決,但是會(huì)TLE。上網(wǎng)搜索了一下大家的解決方案,主要有兩種:DP法和分治法。仔細(xì)消化了一下,感覺(jué)受益匪淺。
首先說(shuō)下DP方法,其實(shí)碰到這種具有很多重復(fù)計(jì)算的子問(wèn)題的問(wèn)題,很容易想到動(dòng)態(tài)規(guī)劃DP。對(duì)于這個(gè)問(wèn)題可以這樣理解:每次都從下標(biāo)0開(kāi)始搜索長(zhǎng)度依次為2、3、……、n的子串,長(zhǎng)度為1的子串肯定是回文。所以可以設(shè)立數(shù)組bool dp[n][n],其中dp[i][i]=true,dp[i][j]表示字符串中從i到j的子串是否是回文,這樣對(duì)于上面的搜索過(guò)程,要判斷str[i-j]要是否回文,可以分為兩種情況:
1. i和j之間沒(méi)有其它字符,所以只要str[i]==str[j]即可。
2. i和j之間還有其它的字符,則要求str[i]==str[j]并且dp[i+1][j-1]=true。這也很好理解,從i+1到j-1的子串是回文,再兩端加上相同的字符,肯定也是回文。
參考代碼如下:
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <cstring>
using namespace std;
char s[5001];
bool dp[5001][5001];
int main(void)
{
int i, j;
int len;
int cnt;
int k;
while(gets(s))
{
len = strlen(s);
cnt = len;
for (i = 0; i < len; ++i)
{
dp[i][i] = 0;
}
for (i = 1; i < len; ++i)
{
for (j = 0; j < len-i; ++j)
{
k = i+j;
dp[j][k] = false;
if (s[j] == s[k])
{
if (j+1 < k-1)
{
if (dp[j+1][k-1])
{
dp[j][k] = true;
++cnt;
}
}
else
{
dp[j][k] = true;
++cnt;
}
}
}
}
printf("%d\n", cnt);
}
return 0;
}
第二種思路是分治法,對(duì)于每個(gè)字符可以以它為中心向兩端擴(kuò)展,依次判斷,這樣同樣避免了很多重復(fù)判斷。在擴(kuò)展過(guò)程中,如果擴(kuò)展到當(dāng)前情況發(fā)現(xiàn)不是回文,這可以停止擴(kuò)展。
擴(kuò)展時(shí)需要分兩種情況:
1.奇數(shù)子串?dāng)U展。例如對(duì)于字符串ababa,從a[2]=a開(kāi)始擴(kuò)展,奇數(shù)擴(kuò)展會(huì)考慮a兩邊的b,是回文,繼續(xù)向外擴(kuò)展。
2.偶數(shù)子串?dāng)U展。對(duì)于同樣的字符串,從a[2]=a開(kāi)始擴(kuò)展 ,都屬擴(kuò)展會(huì)依次考慮子串a[1-2]=ba不是回文,停止擴(kuò)展。
參考代碼如下:
#include<stdio.h>
#include<string.h>
int count=0;
char s[5001];
int main(){
int k,l,r;
int len;
while(gets(s)){
count=0;
len=strlen(s);
for(k=1;k<len-1;k++){
l=k-1,r=k+1;
while(l>=0&&r<len){ //奇數(shù)長(zhǎng)度串
if(s[l--]==s[r++])
count++;
else
break;
}
l=k-1,r=k;
while(l>=0&&r<len){ //偶數(shù)長(zhǎng)度串
if(s[l--]==s[r++])
count++;
else
break;
}
}
if(s[len-1]==s[len-2]) //偶數(shù)長(zhǎng)度的串少算了最后兩個(gè)字符,補(bǔ)上
count++;
count+=len;
printf("%d\n",count);
}
return 0;
}