您的位置:首页 > 其它

ZOJ-2744-Palindromes

2006-05-16 07:29 246 查看
这道题唤起了我遥远的记忆,Palindromes-回文。

我高2时,参加省信息学奥赛复赛时,就遇见了这道题,具体细节是否一样根本记不清了,但是回文我绝对忘不了,那时我觉得这题很简单,便直接开始做,没想到一直到所有时间用完,都没出结果,于是连之后的评审都没参加,黯然离开考场。那之后,我就不太接触信息学奥赛了。其实后来也知道了,当时的算法基本是正确的,可惜因为我放弃bascal,执意用的刚学会的pascal参赛,语法不熟练,导致表达式写错,但这些也许都不能成为理由吧。

觉得可惜的是,当时我虽然已经进了复赛,可我对这个东西完全没有一点了解,认为只是编编算东西的小程序而已,不像今天,互联网上可以很快搜索出很多相关信息。我那时不知道什么是算法数据结构,连使用pascal也只不过因为参赛前的暑假上的课里用的是这个语言。不过今天看来,pascal语言严谨的语法与表达,还是对我产生了很深的影响。

话说回来,事情已经过去了,虽然有点遗憾,比如当时如果能够更深地了解一下有关比赛的背景什么的,不过人生来不得半点假如,尽管遗憾,也参杂着回忆的甘甜,我喜欢我现在的状态,我也会在现在这个起跑线上继续努力。

回到题目本身。题目是要求对于一个输入的字符串,统计它所有可以成为回文的子串的数目,从单个的字母到输入的字符串本身都算子串,限时1秒,就不贴题目了。字符串是从str[0]到str[n-1]一共有n个字符。

1.拿到题目,首先想到的就是,我可以按子串长度展开搜索

首先初试化sum=0

sum+=n,加上长度为1的子串数目

接着是长度为2,3,直到n-1,子串长度和这个长度的子串的总数也很容易求出。对于一个长度大于1的子串,从它的两头开始对比,一旦发现不相等(说明不是回文)就停下,继续对比下一个子串。如果全部相等,就sum++

得解。

虽然从结果和过程来看,应该是正确的,可是用时过多,提交以后TLE。只能重新想办法。

1.按子串长度展开搜索.TLE,time>1s
#include <iostream>
using namespace std;
char str[5001];
int n,sum;

int main()
{
int i,key,flag,step,hp,ep;
while(cin>>str)
{
sum=0;flag=1;
n=strlen(str);
sum+=n;
if(n>=2)
{
for(i=0;i<n-1;i++)
{
if(str[i]==str[i+1])
sum++;
}
}
if(n>2)
{
for(step=3;step<=n;step++)
{
if((step%2)==1)
key=step-1;
else
key=step;
for(i=0;i<=n-step;i++)
{
flag=1;
hp=i;ep=i+step-1;
while(ep>hp)
{
if(str[hp++]!=str[ep--])
{
flag=0;break;
}
}
if(flag)
{
sum++;
}
}
}
}
cout<<sum<<endl;
}
return 0;
}

2.于是我重新构思,从增加代码的重复可用性角度入手,增加了一个子函数,重新写了程序

可以看到,下面这段代码虽然同样实现的是第一种算法,但却有种简约的美,没有多余的枝节判断与处理,将step>=2的情况进行了统一的处理,所以这段代码比上面的要漂亮很多。

可惜因为复杂度和第一种方法相差无几,所以依然TLE

2.按子串长度展开搜索的改进.TLE,time>1s
#include <iostream>
using namespace std;
char str[5001];
int n,sum;

bool check(int hp,int ep)
{
while(ep>hp)
{
if(str[hp++]!=str[ep--])
{
return false;
}
}
return true;
}

int main()
{
int i,j,key;
while(cin>>str)
{
sum=0;
n=strlen(str);
sum+=n;
for(i=0;i<n-1;i++)
{
for(j=i+1;j<n;j++)
{
if(check(i,j))
sum++;
}
}
cout<<sum<<endl;
}
return 0;
}

3.为了AC,我重新构思算法,完全抛开之前的算法,这次我成功的减少了一个层循环。

外层循环从1,到n-2,对于中间每一个str[]调用两次子函数check:
checkstr(i,i);
checkstr(i-1,i);
check函数以两个入口变量是否相等,做不同处理。相等时,以输入的字符位置i为中心从内向外检查长度为奇数的子串;不相等时,对以i-1,i为中心的长度为偶数的字符串进行检查。

从内向往检查的原则是,一旦两个字符相等,便sum++,然后两头的指针各向外移动一步,再检查,直到不相等,便说明不能再有回文字符串了,便停止,或者两头指针超过了[0,n-1]的范围时,也停止。然后中心移到下一个字符。对每个字符都进行奇中心和偶中心两遍搜索。

终于AC,time=0.09s,memory=388k。可见算法的改进对于程序来说,的确是数量级的改进,很多时候甚至能比硬件的改进发挥更大的作用!这道题加深了我对算法重要性的认识。

输出输入改为c的格式是为了不#include<iostream>,可以节省一点内存,这样在用时相同的情况下,内存用的少的排名比较靠前嘻嘻

3.对除两端之外的字符做从内到外的扩散式搜索。AC,time=0.09s,memory=388k
#include <stdio.h>
#include <string.h>

char str[5001];
int n,sum;

void checkstr(int a,int b)
{
int hp,ep;
if(a==b)
{
hp=a-1;
ep=a+1;
while(hp>=0 && ep<=n-1)
{
if(str[hp--]==str[ep++])
sum++;
else
{
hp=-1;
return;
}
}
}
else
{
hp=a;ep=b;
while(hp>=0 && ep<=n-1)
{
if(str[hp--]==str[ep++])
sum++;
else
{
hp=-1;
return;
}
}
}
return;
}

int main()
{
int i;
while(scanf("%s",str)!=EOF)
{
n=strlen(str);
sum=n;
for(i=1;i<n-1;i++)
{
checkstr(i,i);
checkstr(i-1,i);
}
if(str[n-2]==str[n-1])
{
sum++;
}
printf("%d/n",sum);
}
return 0;
}

END:但是说回来,到现在为止,最快的家伙用时是0.03秒,说明对于我上面的程序,依然有很大的改进的余地,我想思路应该从统一奇偶两种搜索入手,减少对子函数的调用次数,得到更简洁的方法呵呵,有空再改。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: