北京航空航天大学2014级C++第一次练习*L题解题报告

*L-大只的回文数

题目描述

小小的大只没做中秋节热身赛,这两天看到那道回文数的题目颇为有趣。她决定进行修改。

 

输入格式:

题目有多组数据
每行两个数a,b,以空格隔开。

 

输出格式:

每行一个ans,表示[a,b]中回文数的个数。

 

Sample in:

1 1

Sample out:

1

 

数据范围:

0<a<=b且在int范围内.数据组数<=100000.

 

Hint:

时限3s哦~~不卡cin,cout的时间

 

解题思路:

一开始看到这道题,常规思路是从a循环到b,判断每一个数是不是回文数。但是由于算法的时间复杂度是O(k*n)级别的,于是果断TLE…...

----------------------------------------------------误入歧途的分割线---------------------------------------------------

经过分析可以发现,如果每次输入一组a和b都要循环一次,就会有很多数被重复判断。于是自然就想到了将所有回文数顺序储存在一个表中,每次找到>=a和<=b的回文数,将它们的序号相减再加一就得到了答案,这样只需要花费一次从1到2147483647查找回文数的时间,每组a和b就不用判断了。这个算法的时间复杂度是O(k*+n),再次TLE……

 

顺着这个思路再分析下去,既然在一串数中回文数的个数远小于非回文数的个数,为何要用查找这么低效率的方法呢……其实可以顺序构造从1到2147483647的所有回文数,再储存在表中。现在的问题就是构造回文数了。任取一个回文数384483,将它从中间分成384 | 483两部分。可以看到右半部分是左半部分的倒序。于是所有偶数位回文数都是一个数left与left的倒序reverse(left)组成的。再考虑奇数位回文数38483。它依旧可以被分成384 | (4)83两部分,只不过是把中间的4删掉一个罢了。于是所有的奇数位回文数都可以表示为left与left/10的倒序reverse(left/10)。然后我们就可以开始构造回文数啦~~

 

核心代码(构造回文数):

unsigned int my_map[125000];    //储存回文数

unsigned int my_reverse(unsigned intx)    //将一个数倒过来

{……}

……

for(length_l=1,length_r=1;my_map[index-1]<2147483647;length_l==length_r?left=power[length_l],length_r++:length_l++)

{

   for(;left<power[length_l+1]&&my_map[index-1]<2147483647;left++,index++)

       my_map[index]=left*power[length_r]+my_reverse(left)%power[length_r];

}

 

这个算法的时间复杂度是O(k*),由于此题目中n远大于k,勉强水过……

----------------------------------------------------步入正轨的分割线---------------------------------------------------

其实这道题并没有这么复杂。在对回文数进行进一步分析后,可以发现一个规律:

 

1~9的回文数个数:9

10~99的回文数个数:9

100~999的回文数个数:90

1000~9999的回文数个数:90

10000~99999的回文数个数:900

100000~999999的回文数个数:900

1000000~9999999的回文数个数:9000

10000000~99999999的回文数个数:9000

100000000~999999999的回文数个数:90000

1000000000~9999999999的回文数个数:90000

 

于是对于一个t位数的数s,先判断从1*到s的回文数个数,再与比它位数少的所有回文数的个数相加,就可以得出从1到s的回文数个数。

 

由于之前已经知道回文数的结构(left+reverse(left)或left+reverse(left/10),”+”表示连接),因此对于一个t位数的数s,取s的左半部分left_s(长度为(t+1)/2),构造一个回文数s’,容易看出s’是在数轴上距离s最近的回文数。

 

回到这道题。判断a和a’、b和b’的大小,取<=b的第一个回文数b’’和>=a的第一个回文数a’’,求出1到a’’的回文数个数sum_a和1到b’’的回文数个数sum_b,则ans=sum_b-sum_a+1

 

参考代码:

#include <cstdio>

using namespace std;

 

unsigned int creverse(unsigned int);    //声明函数creverse用来倒置一个整数

 

int main()

{

//声明变量太多换行写了

unsigned inta,b,len1,len2,a0,b0,ans,

//将储存在数组len[n]中

len[11]={0,1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000},

//将1~中所有回文数的个数储存在数组num[n]中

   num[10]={0,9,18,108,198,1098,1998,10998,19998,109998};

 

   while(scanf("%u%u",&a,&b)!=EOF)

{

    //计算b的位数len2

       for(len2=10;len2>0;len2--)

       {

           if(b/len[len2]!=0)

                break;

       }

 

       //计算a的位数len1

for(len1=10;len1>0;len1--)

       {

           if(a/len[len1]!=0)

                break;

       }

 

       //给中间变量a0,b0赋值

       b0=b/len[len2/2+1];

       a0=a/len[len1/2+1];

       //计算结果ans

       ans=num[len2-1]    //加上1~中回文数的个数

          -num[len1-1]    //减去1~中回文数的个数

          +b0-len[(len2+1)/2]    //加上~b’中回文数的个数-1

          -a0+len[(len1+1)/2]    //减去~a’中回文数的个数-1

          +(b%len[(len2+3)/2]>=creverse(b0))   //若b’是b’’则ans+1

          -(a%len[(len1+3)/2]>creverse(a0));   //若a’不是a’’则ans-1

 

       printf("%u\n",ans);

    }

 

   return 0;

}

 

以上

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值