ZOJ-2744-Palindromes

这道题唤起了我遥远的记忆,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秒,说明对于我上面的程序,依然有很大的改进的余地,我想思路应该从统一奇偶两种搜索入手,减少对子函数的调用次数,得到更简洁的方法呵呵,有空再改。

你可能感兴趣的:(数据结构,算法,语言,HP,pascal,iostream)