I am a teacher!

导航

C语言程序设计100例之(43):Excel地址

例43  Excel地址

问题描述

Excel是常用的办公软件。在Excel表格中,每个单元格都有唯一的地址表示。比如:第12行第4列表示为“D12”,第5行第255列表示为“IU5”。

事实上,Excel提供了两种地址表示方法,还有一种表示法叫做RC格式地址。 第12行第4列表示为“R12C4”,第5行第255列表示为“R5C255”。

编写程序,实现从RC地址格式到常规地址格式的转换。

输入格式

第1行是一个正整数n(n<100),表示接下来有n行输入数据。

接着输入的n行数据是RC格式的Excel单元格地址表示法。

输出格式

输出n行数据,每行是转换后的常规地址表示法。

输入样例

2

R12C4

R5C255

输出样例

D12

IU5

        (1)编程思路。

        首先将输入的RC格式地址中的行和列的值求出来。例如,输入R5C255后,可求出行值row=5,列值col=255。

        之后将列值col转换成26进制表示的数,方法为将col不断除以26,记下余数,直到商为0,每个余数保存到数组d中。例如,255转换为26进制数是一个两位数,高位为9(保存到d[1]中),低位为21(保存到d[0])中。然后将数字映射为字母,1对应A,2对应B,…,9对应I,…,21对应U,…,26对应Z。这样,255列表示为IU。

        但在具体处理时,由于1~26表示A~Z,因此得到的余数若为0,则应对应为26,即26不进位,由此需向前借1位。例如,列值col=52时,转换为26进制数码d[1]=2,d[0]=0,此时由于d[0]<=0,d[0]=d[0]+26=26,d[1]=d[1]-1=1,再对应表示为AZ。

(2)源程序。

#include <stdio.h>

#include <string.h>

int main()

{

    int n;

    scanf("%d",&n);

    while (n--)

       {

           char str[24],colstr[9];

           int row,col,d[7],i,k,len,flag;

           scanf("%s",str);

              row=0; col=0; flag=0;

        for (i=1;str[i]!='\0';i++)

              {

            if (str[i]!='C')

                     {

                            if (flag==0)

                                   row=row*10+str[i]-'0';

                            else

                                   col=col*10+str[i]-'0';

                     }

                     else

                            flag=1;

              }

              len=0;

              while (col!=0)

              {

                     d[len++]=col%26;

                     col/=26;

              }

              for (i=0;i<len-1;i++)

                     if (d[i]<=0)

                     {

                d[i]=d[i]+26;

                            d[i+1]--;

                     }

              if (d[len-1]==0) len--;

              k=0;

              for (i=len-1;i>=0;i--)

            colstr[k++]=d[i]-1+'A';

              colstr[k]='\0';

        printf("%s%d\n",colstr,row);

       }

       return 0;

}

习题43

43-1  Hexadecimal Numbers

        本题选自POJ题库 (http://poj.org/problem?id=1715)

Description

The base of the hexadecimal system is 16. In order to write down numbers in this system one uses 16 digits 0,1,...,9,A,B,C,D,E,F. The capital letters A,..,F stands for 10,..,15, respectively. For instance the value of the hexadecimal number CF2 is 12 * 162 + 15 * 16 + 2 = 3314 in the decimal system. Let X be the set of all positive integers whose hexadecimal representations have at most 8 digits and do not contain repetitions of any digit. The most significant digit in such a representation is not zero. The largest element in X is the number with the hexadecimal representation FEDCBA98, the second largest is the number FEDCBA97, the third is FEDCBA96 and so forth.

Write a program that: finds the n-th largest element of X;

Input

The first line of the file standard input contains integer n in the decimal representation. n does not exceed the number of elements of X.

Output

Your program should output the n-th largest element in X in the hexadecimal representation.

Sample Input

11

Sample Output

FEDCBA87

        (1)编程思路。

        本题是给定16进制数的16个数码,求第k大的数,要求数的长度最大为8,并且每个数码互不相同。

        第1大的数为FEDCBA98,第2大的数为FEDCBA97,第2大的数为FEDCBA96,…,倒数第2大的数为1,最小的数为0。

        先确定第k大的数的位数,最多为8位。8位数一共有15*15*14*13*12*11*10*9=486486000个,最高位不能为0,在1~F这15个数码中任取一个作为最高位,第2位在剩下的15个数码中任取1位,第3位在剩下的14个数码中任取1位,…,第8位在剩下的9个数码中任取1位,所以8位数的个数为486486000个。同理,7位数的个数为15*15*14*13*12*11*10=54054000个;6位数的个数为15*15*14*13*12*11=5405400个;5位数的个数为15*15*14*13*12=491400个;4位数的个数为15*15*14*13=40950个;3位数的个数为15*15*14=3150个;2位数的个数为15*15=225个;1位数的个数为15个(指非0,若包括0,则有16个)。上述所有数加起来为546481140,即第546481141大的数为0。

        定义数组int m[10]={0,15,225,3150,40950,491400,5405400,54054000,486486000};保存各长度数的个数。由上面分析知,第486486000大的数是最小的8位数10234567,第486486001大的数应该就是最大的7位数FEDCBA9;第486486000+54054000=540540000大的数一定是最小的7位数1023456,第540540001大的数一定是最大的6位数FEDCBA。由此可以确定第k大的数的位数,若k<=486486000,则位数为8,同时可确定也是第k大的8位数;若486486000<k<=486486000+54054000,则位数为7,同时可确定其是第k-486486000的7位数;…,以此类推。

        确定第k大的数也是第n大的len位数后,再从高位往低位进行处理。若最高位确定为F,则后7位一共有15*14*13*12*11*10*9,简记为mul(15,7),表示从15~9这连续的7个数相乘,也就是说,若k<=mul(15,7),则第k大的8位数最高位一定为F。可以验证第mul(15,7)=32432400大的数一定为F0123456,则第32432401大的数一定为EFDCBA98。若mul(15,7)<k<=2*mul(15,7),则最高位一定为E,若2*mul(15,7)<k<=3*mul(15,7),则最高位一定为D,…,即最高位为k/mul(15,7)对应16个数码的映射,映射为0—F,1—E,2—D,15—0。

        确定了最高位后,再确定次高位。此时,需要将k%mul(15,7),不妨设结果为k1。若确定次高位是剩下的15个数码中的最大,则后面6位一共可表示14*13*12*11*10*9=2162160,简记为mul(14,6),也就是说若k1<=mul(14,6),则第k大的数的次高位一定是剩下15个数码中最大的,若mul(14,6)<k1<=2*mul(14,6),则第k大的数的次高位一定是剩下15个数码中第2大的,若2*mul(14,6)<k1<=3*mul(14,6),则第k大的数的次高位一定是剩下15个数码中第3大的,…,即次高位为k1/mul(14,6)对应剩下15个数码的映射。可以验证第34594560(即32432400+2162160)大的数一定为EF012345,第34594561大的数一定为EDFCBA98。这是由于k大于mul(15,7),所以k的最高位为E,k1=k%mul(15,7)=2162161,k1>mul(14,6),所以次高位是除去E后第2大的数码,所以为D。

        同理,可确定之后的各位。

      (2)源程序。

#include <stdio.h>

int mul(int x,int y)

{

    int p=1;

    while (y--)

    {

        p*=x;

        x--;

    }

    return p;

}

int main()

{

    int m[10]={0,15,225,3150,40950,491400,5405400,54054000,486486000};

    char num[17]="0123456789ABCDEF";

    int b[10],vis[20];

    int n;

    while (scanf("%d",&n)!=EOF)

    {

        if (n>=546481141)

        {

           printf("0\n");

           continue;

        }

        int len=8;

        while (n>m[len])

        {

            n-=m[len];  len--;

        }

        n--;

        int i,j;

        for (i=len;i>=1;i--)

        {

            int t=mul(15+i-len,i-1);

            b[i]=n/t;

            n%=t;

        }

        for (i=1;i<=16;i++) 

            vis[i]=0;

        for (i=len;i>=1;i--)

        {

            int s=0;

            for (j=1;j<=16;j++)

            {

               if (vis[j]==0)

               {

                   s++;

                   if (s==b[i]+1)

                   {

                      vis[j]=1;

                      break;

                   }

               }

            }

            printf("%c",num[16-j]);

        }

        printf("\n");

    }

    return 0;

}

43-2  识别码

问题描述

某系统中给每件物品编制一个唯一的识别码,该识别码由26个小写字母中最多50个字符组成。任何给定代码的字符集均可随意选择。但一旦选择了一组字母,在更改该组字母之前,将使用从该组字母派生的所有可能代码作为识别码。

例如,假设选定的字符集包含3个“a”、2个“b”和1个“c”,那么由这组字母编制的识别码按字母顺序依次如下:

aaabbc

aaabcb

……

abaabc

abaacb

ababac

……

cbbaaa

编写一个程序来帮助发布这些识别码。输入一个不超过50个小写字母(可能包含重复字符)组成的字符串,输出其下一个排列字符串。

输入

输入由一系列行组成,每行包含一个表示给定代码的字符串。整个文件将以一行结尾,该行由一个#组成。

输出

求输入字符串的下一个排列串。若不存在,就输出No Successor。

输入样例

abaacb

cbbaa

#

输出样例

ababac

No Successor

         (1)编程思路。

        求输入字符串的下一个排列字符串的方法如下,为描述方便,设字符串中字符记为1~n。

        设P是1~n的一个全排列:p=p1p2……pn

                                                   =p1p2……pj-1pjpj+1……pk-1pkpk+1……pn

        1)从排列的右端开始。找出第一个比右边数字小的数字的序号j(j从左端开始计算)。即 j=max { i | pi < pi+ 1}。

        2)在pj的右边的数字中。找出全部比pj大的数中最小的数字pk,即 k=max{i|pi>pj}。由于右边的数从右至左是递增的,因此k是全部大于pj的数字中序号最大者。

        3)交换pj,pk。

        4)再将pj+1……pk-1pkpk+1……pn倒转得到排列

        p’=p1p2…..pj-1pjpn…..pk+1pkpk-1…..pj+1。

        这就是排列p的下一个排列。

        例如,设有排列:1,3,5,4,2,其中5-4-2即为递增序列。找到a[1]=3;从上述序列中找一个比a[1](3)大的最小数(4),并将且交换这两个数。于是1,3,5,4,2变换为1,4,5,3,2,再将4后面的数字递增排列,即1,4,2,3,5。

        因此,得到1,3,5,4,2的下一个排列为1,4,2,3,5。

       (2)源程序1。

#include <stdio.h>

#include <string.h>

int theNext(char a[],int n)

{

    int len =n-1,front;

       int flag=0;

    while (len>0)

    {

        if(a[len-1]<a[len])

        {

            front = len-1;

            flag=1;

                     break;

        }

        len--;

    }

       if (! flag)

       return 0;

    len =n-1;

    int min=200;

    while (len>front)

    {

        if(a[len]>a[front])

        {

            min=len;

                     break;

        }

        len--;

    }

    char temp=a[min];

    a[min]=a[front];

    a[front]=temp;

    int i,j;

    for(i=front+1;i<n;i++)

    {

        for(j=i;j<n;j++)

        {

            if(a[i]>a[j])

            {

                temp=a[i];

                a[i]=a[j];

                a[j]=temp;

            }

        }

    }

    return 1;

}

int main()

{

       char str[51];

       while (scanf("%s", str)&& str[0]!='#')

       {

              if(theNext(str, strlen(str)))

                     printf("%s\n", str);

              else

                     printf("No Successor\n");

       }

       return 0;

}

      (3)源程序2。

       也可以用库函数next_permutation直接求某个排列的下一个排列,编写的源程序如下。

#include <stdio.h>

#include <algorithm>

using namespace std;

int main()

{

         char str[51];

         while (scanf("%s", str)&& str[0]!='#')

         {

                   if(next_permutation(str, str + strlen(str)))

                            printf("%s\n", str);

                   else

                            printf("No Successor\n");

         }

         return 0;

}

43-3  排列顺序

问题描述

1~n这n(2 <= n <= 8,000)个数排成一行,给出从第2个数到最后一个数每个数之前比它小的数的个数,求出这n个数的排列顺序。

输入

输入包括两行,第1行为整数n;第2行有n-1个整数a1,a2,…,ai,…,an-1,其中ai为第I+1个数之前的i个数比第i+1个数小的数的个数。

输出

输出n个数的排列顺序,每个数占1行。

输入样例

5

1 2 1 0

输出样例

2

4

5

3

1

       (1)编程思路。

        我们先来看看样例是如何确定的。有1~n这5个数,从第2个数到最后一个数每个数之前比它小的数的个数分别为1 2 1 0。从最后一个数往前依次确定每个数。最后一个数之前比它小的数为0,显然最后一个数只能填最小的数1,否则,它前面至少有1个1比它小;倒数第2个数之前比它小的数有1个,1已填过,若倒数第2个数填 2,则前面的数都比它大,不满足,因此考虑填 3,此时前面只有1个2比它小,满足要求;倒数第3个数之前比它小的数为2,1和3已填过,若填写2或4,则前面比它小的数或为0或为1,达不到2,因此只能填写5,前面存在2和4比它小;倒数第4个数之前比它小的数为1,1已填过,填写2不符合要求,3已填过,填写4,之前存在2比它小,正好满足要求;剩下的一个未填写的数字2,正好填在第1位。由此,得到填写序列为2,4,5,3,1。

        用循环模拟上面给出的填数过程,从后往前依次确定序列中的第n到第2个数应该填写1~n中哪一个数。定义数组int vis[8001];保存每个数是否填写过,若数i已填写,则置vis[i]=1。对于每个位置p应填写的数,从1~n中统计vis[i]==0的数的个数cnt,若统计到k时,cnt正好等于a[p]+1,即比k小的数有a[p]个,则p位置就填写k,同时置vis[k]=1。

        之后,将1~n中仅有的vis[i]==0的数填在序列的第1个位置。

       (2)源程序。

#include <stdio.h>

#include <string.h>

#define  maxn 8001

int vis[maxn];

int a[maxn], ans[maxn];

int main(void)

{

    int n,i,k,t,cnt;

    scanf("%d",&n);

    memset(vis, 0, sizeof(vis));

    for (i=1; i<n; i++)

              scanf("%d", &a[i]);

    t = n;

    for (i=n-1; i>=1; i--)       //  从后往前依次确定序列中的第n到第2个数

    {

        cnt=-1, k=1;

        for (k=1; k<=n; k++)

        {

            if (!vis[k]) cnt++;   // 统计未确定位置的数中有多少个比k小

            if (cnt == a[i])   

                     {

                            vis[k] = 1;     // 标记数k已确定位置

                            break;

                     }

        }

        ans[t--] = k;

    }

    for (i = 1; i <= n; i++)   // 确定原序列中的第1个数

        if (! vis[i])

              {

                     ans[t] = i;

                     break;

              }

    for (i = 1; i <= n; i++)

        printf("%d\n",ans[i]);

    return 0;

}

posted on 2022-01-07 19:22  aTeacher  阅读(645)  评论(0编辑  收藏  举报