C语言程序设计100例之(51):第n个回文数


例51  第n个回文数

问题描述

回文是向前和向后读相同的单词、数字或短语。例如,“anna”是一个回文。数字也可以是回文(例如151或753357)。此外,数字可以按大小排序。前几个回文数字是:1,2,3,4,5,6,7,8,9,11,22,33,…。

数字10不是回文(即使您可以将其写为010),但不允许将零作为前导数字。

输入

输入由一系列行组成,每行包含一个整数值i(1<=i<=2*10^9)。该整数值i表示要输出的回文数的序号,其中1表示第一个回文数(1),序号2表示第二个回文数(2),依此类推。输入由包含0的行终止。

输出

对于每个输入值i,输出第i个回文数。

输入样例

12

24

输出样例

33

151

        (1)编程思路。

        1位和2位的回文数各有9个(最高位为1~9),3位和4位的回文数各有90个(高两位为10~99),5位和6位的回文数各有900个(高3位为100~999),…。

        定义数组long long f[30]保存k位回文数的基数10^((k-1)/2),即f[1]=f[2]=1,f[3]=f[4]=10,f[5]=f[6]=100,…。

        计算出第n个回文数是几位数,一个简单循环即可完成。例如,n=24,由于n>9*f[1],所以n至少是2位以上的回文数,len=2,n=n-9=15;由于n>9*f[2],所以n至少是3位以上的回文数,len=3,n=n-9=6;由于n<9*f[3],所以n是一个3位回文数。

        确定了第n个回文数对应的位数后,将数对半,对半后,对前半段进行填数,后半段的数字只要将前半段反向输出即可。

        例如,第24个回文数是3位回文数中的第6个数,3位回文数前半段为前2位(10~99),第6个数为15,后半段为1,故第24个回文数为151。

(2)源程序。

#include

#include

int main()

{

    long long f[30]={0,1,1};

    int i,j;

    for (i=3; i<30; i++)

    {

        if (i&1) f[i] = f[i-1]*10;

        else f[i] = f[i-1];

    }

    long long n;

    while (scanf("%lld",&n) && n!=0)

    {

        int k = 1;                  //  k表示回文串数字的位数

        while(n > f[k]*9)

        {

            n-=9*f[k++];

        }

        int mid = (k+1)/2;

        long long ans = 1;

        for (i=1, j=0; i<=mid; i++)   // 第一位到中间那一位

        {

            if (i==1) j=1;      //  i为1,因为首位不能为0,所以要从1开始

            else  j = 0;       //  j表示第i位要填的数字

            while (n > f[k])

            {

               n -= f[k];

               j++;

            }

            k -= 2;    // 除了第i位和i相对称的那一位,中间还有几位

            if (i==1)  ans = j;

            else  ans = ans*10 + j;

        }

        printf("%lld",ans);

        if (k&1) ans /= 10;    //  位数为奇数,最中间的数只输出一次

        while (ans)

        {

           printf("%lld",ans%10);

           ans /= 10;

        }

        printf("\n");

    }

    return 0;

}

习题51

51-1  Moo

        本题选自洛谷题库 (https://www.luogu.org/problem/ P1885)

题目描述

奶牛 Bessie 最近在学习字符串操作,它用如下的规则逐一的构造出新的字符串:

S(0) =S(0)= moo

S(1) =S(0)+ m + ooo + S(0) = moo + m ++ ooo + moo = moomooomoo

S(2) = S(1)+ m + oooo + S(1) =moomooomoo + m + oooo + moomooomoo = moomooomoomoooomoomooomoo

Bessie 就这样产生字符串,直到最后产生的那个字符串长度不小于读入的整数 N 才停止。

通过上面观察,可以发现第 k 个字符串是由:第 k-1个字符串 + m+ (k+2 个 o))+ 第 k?1 个字符串连接起来的。

现在的问题是:给出一个整数 N (1≤N≤109 ),问第 N 个字符是字母 m 还是 o?

输入格式

一个正整数 N。

输出格式

一个字符,m 或者 o。

输入样例

11

输出样例

        (1)编程思路。

        设字符串S(i)的长度为Len[i],由字符串的构造规律知:

        Len[0]=0+1+2   (前面没有子串,有1个m和2个0)

        Len[1]=len[0]+1+3+len[0]  (前后均有子串S[0],中间有1个m和3个O)

        更一般地,Len[k]=len[k-1]+1+(k+2)+len[k-1] = 2*len[k-1]+(k+1)+2

        要求第 N 个字符是字母 m 还是 o,先得看达到N个字符的最短字符串是S(?)。

        由样例解释知:字符串 S(0) 是moo,现在要求第11 个字符,显然字符串 S(0) 不够长;同样S(1) 的长度是 10,也不够长; S(2) 的长度是25,够长了,S(2) 的第11 个字符是 m,所以答案就输出 m。

        要求达到N个字符的最短字符串是S(?),可以采用如下的循环

        int  t=0,k=1;    // 初始值,表示字符串为S(0),其中t表示前面子串的长度

        while (n>2*t+k+2)

        {

            t=2*t+2+k;

            k++;

        }

        退出循环后,正好求得满足要求的字符串S(k-1),这个字符串有三个部分构成:第1部分为子串S(k-2),长度为t,这1部分都存在(即n的值肯定大于t);第2部分为1个m加上k+1个o,如果n的值在这个范围中,则可以直接确定第N个字符是什么,当n==t+1时,字符为m,当n<=t+k+2,字符为o(从t+2到t+k+2这k+1个字符是o);第3部分又是子串S(k-2),此时n>t+k+2,去掉第1部分和第2部分后,n=n-(t+k+2),再到子串S(k-2)中去确定第n个字符是什么,继续进行上面的循环。

        (2)源程序。

#include

int main()

{

    int n;

    scanf("%d",&n);

    while (1)

    {

        int t=0,k=1;

        while (n>2*t+k+2)

        {

            t=2*t+2+k;

            k++;

        }

        if (n==t+1)

        {

           printf("m\n");

           break;

        }

        if (n<=t+k+2)

        {

            printf("o\n");

            break;

        }

        n=n-t-k-2;

        t=0;

        k=1;

    }

    return 0;

}

51-2  无限字符串

问题描述

给定一个字符串,让后面的字符旋转一次(每一次正确的旋转,最后一个字符都会成为新的第一个字符)。也就是说,给定一个初始字符串,之后的每一步都会增加当前字符串的长度。

例如,初始字符串为COW,第1次旋转后变为COWWCO,第2次旋转后变为 COWWCOOCOWWC,…。

给定初始字符串和索引值N,请确定旋转生成的无限字符串中位置N的字符。

输入格式

输入包括一个字符串和一个整数N。该字符串包含最多30个大写字母,N≤1018

输出格式

输出从初始字符串生成的无限字符串中的位置N的字符。第一个字符是 N=1。

输入样例

COW  8

输出样例

        (1)编程思路。

        观察无限字符串的构造规律可以发现:每一次旋转,增长的部分的第1位跟原部分的最后一位相同,增长部分的第2位到最后一位跟原部分的第1位到倒数第2位相同。

        以样例为例:COW--->COWWCO---->COWWCOOCOWWC。可以发现第1次旋转后,生成字符串的第1位到第2位与第5位到第6位相同。第2次旋转增长后,字符串的第1位到第5位与第8位到第12位相同。所以可以不断的把前面一半不用的删除掉,最后剩下初始字符串时输出对应字符即可。

        以样例为例描述计算过程。N=8,原始字符串长度len=3,第1次增长后串长为6,第2次增长后串长为12。由于,3<(N=8)<12,因此可以将前面一般丢掉N=N-12/2=6,由于剩余字符串OCOWWC的最高位是前一半的最低位,也需丢掉(或相当移到最低位),即N=N-1=5;此时3

        再以N=11为例,原始字符串长度len=3,第1次增长后串长为6,第2次增长后串长为12。由于,3<(N=11)<12,因此可以将前面一般丢掉N=N-12/2-1=4;此时3

       (2)源程序。

#include

#include

int main()

{

    char str[31];

    long long n;

    scanf("%s%lld",str,&n);

    long long len=strlen(str);

    while (len

    {

        long long i=len;

        while (n>i*2) i*=2;

        n=n-(i+1);

        if (n==0) n=i;

    }

    printf("%c",str[n-1]);

    return 0;

}

51-3  无穷的序列

题目描述

有一个无穷序列如下:

110100100010000100000…

请你找出这个无穷序列中指定位置上的数字。

输入格式

第一行一个正整数N(N≤1500000),表示询问次数;

接下来的N行每行一个正整数Ai(Ai≤10^9),Ai表示在序列中的位置。

输出格式

N行,每行为0或l,表示序列第Ai位上的数字。

输入样例

14

输出样例

         (1)编程思路。

        先将整个无穷序列110100100010000100000……分为如下无限个区间

        1  10  100  1000  10000  100000……

        显然第n个区间有n个数字,并且1只出现在每个区间的第一位。

        前n个区间累计一共有(n+1)*n/2个数字。所以每1个数字“1”出现的位置一定是在(n*n+n)/2+1,考虑到第1位的数字“1”,可修改位置为(n*n-n)/2+1。

        对于每个输入的数字ai,如果 (n*n-n)/2+1=a( n ≥0 )有整数解就是1,否则就是0。

        方程 n2-n+2-2a=0的解为 n=(1+sqrt(8*a-7))/2   或  n=(1-sqrt(8*a-7))/2

        非负整数解只可能为  n=(1+sqrt(8*a-7))/2

        若 n*n-n==2*a-2,则数字为“1”,否则为“0”

       (2)源程序。

#include

#include

int main()

{

    int n,ai,t;

    scanf("%d",&n);

    while (n--)

    {

        scanf("%d",&ai);

        t=(1+sqrt(8*ai-7))/2;

        if (t*t-t==2*(ai-1))

            printf("1\n");

        else

            printf("0\n");

    }

    return 0;

}

相关