*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;
}
以上