算法竞赛入门 (一)语言篇 数组和字符串 2

三、竞赛题目选讲

例题3-2 WERTYU

把手放在键盘上时,稍不注意就会往右错一位。这样,输入Q会变成输入W,输入J会变成输 入K等。输入一个错位后敲出的字符串(所有字母均大写),输出打字员本来想打出的句子。输入保证合法,即一定是错位之后的字符串。例如输入中不会出现大写字母A。

样例输入:
O S, GOMR YPFSU/
样例输出:
I AM FINE TODAY

我的思路:这样的打字员赶紧滚犊子完事

同例题3-1,可以利用 getchar()  一边输入,一边输出。但问题卡在了 如何变换回正确的? switch 或 if 都太麻烦

书的思路:使用常量数组

#include<stdio.h>
 char s[] = "`1234567890-=QWERTYUIOP[]\ASDFGHJKL;'ZXCVBNM,./";
 int main() {
    int i, c;
    while((c = getchar()) != EOF)
    {
        for (i=1; s[i] && s[i]!=c; i++); //找错位之后的字符在常量表中的位置
        if (s[i])
                putchar(s[i-1]); //如果找到,则输出它的前一个字符
        else
            putchar(c);
    }
    return 0;
 }

 # 常量数组并不需要指明大小,编译器可以完成计算

 例题3-3 回文词

输入一个字符串,判断它是否为回文串以及镜像串。输入字符串保证不含数字0。所谓 回文串,就是反转以后和原串相同,如abba和madam。所有镜像串,就是左右镜像之后和原

串相同,如2S和3AIAE。注意,并不是每个字符在镜像之后都能得到一个合法字符。在本题 中,每个字符的镜像如图3-3所示(空白项表示该字符镜像后不能得到一个合法字符) 

 

输入的每行包含一个字符串(保证只有上述字符。不含空白字符),判断它是否为回文 串和镜像串(共4种组合)。每组数据之后输出一个空行。
样例输入:
NOTAPALINDROME
ISAPALINILAPASI
2A3MEAS
ATOYOTA
样例输出:
NOTAPALINDROME -- is not a palindrome.
ISAPALINILAPASI -- is a regular palindrome.
2A3MEAS -- is a mirrored string.
ATOYOTA -- is a mirrored palindrome.

分析:题目提到了 不包含空白字符 —— 可以安全的用 scanf 进行输入

可以采用上方例题提到的 常量数组解决

#include<stdio.h>
#include<string.h>
#include<ctype.h>
const char* rev = "A   3  HIL JM O   2TUVWXY51SE Z  8 ";
const char* msg[] = {"not a palindrome", "a regular palindrome", "a mirrored string","a mirrored palindrome"};

char r(char ch) {
      if(isalpha(ch))
        return rev[ch - 'A'];
      return rev[ch - '0' + 25];
}
int main() {
      char s[30];
      while(scanf("%s", s) == 1) {
            int len = strlen(s);
            int p = 1, m = 1;
            for(int i = 0; i < (len+1)/2; i++) {
                        if(s[i] != s[len-1-i])
                        p = 0; //不是回文串
                        if(r(s[i]) != s[len-1-i])
                            m = 0; //不是镜像串
                        }
            printf("%s -- is %s.

", s, msg[m*2+p]);

    }
   return 0;
}

自定义函数char r(char ch),参数ch是一个字符,返回值是ch的镜像字符。这是因为该常量数组中前26项是各个大写字母的镜像,而后10个是数字1~9的镜像(数 字0不会出现),所以需要判断ch是字母还是数字

isalpha来判断字符是否为字母,类似的还有idigit、isprint等,在ctype.h中定义。

由于ASCII码表中大写字母、小写字母和数字都是连续的,如果ch是大写字母,则ch-'A'就是 它在字母表中的序号(A的序号是0,B的序号是1,依此类推);类似地,如果ch是数字, 则ch-'0'就是这个数字的数值本身(例如'5'-'0'=5)

常量数组msg,事实上,这是一个字符串数组,即二维字符数组

 # 头文件ctype.h中定义的isalpha、isdigit、isprint等工具可以用来判断字符 的属性,而toupper、tolower等工具可以用来转换大小写。如果ch是大写字母,则ch-'A'就是 它在字母表中的序号(A的序号是0,B的序号是1,依此类推);类似地,如果ch是数字, 则ch-'0'就是这个数字的数值本身

char* a = "abc" 和 char a[] = "abc" 之间的区别

 例题3-4 猜数字游戏的提示

实现一个经典"猜数字"游戏。给定答案序列和用户猜的序列,统计有多少数字位置正确 (A),有多少数字在两个序列都出现过但位置不对(B)。
输入包含多组数据。每组输入第一行为序列长度n,第二行是答案序列,接下来是若干 猜测序列。猜测序列全0时该组数据结束。n=0时输入结束

样例输入:
4
1 3 5 5
1 1 2 3
4 3 3 5
6 5 5 1
6 1 3 5
1 3 5 5
0 0 0 0
10
1 2 2 2 4 5 6 6 6 9
1 2 3 4 5 6 7 8 9 1
1 1 2 2 3 3 4 4 5 5
1 2 1 3 1 5 1 6 1 9
1 2 2 5 5 5 6 6 6 7
0 0 0 0 0 0 0 0 0 0
0
样例输出:
Game 1:

(1,1)

(2,0)

(1,2)

(1,2)

(4,0)

Game 2:

(2,4)

(3,2)

(5,0)

(7,0)

分析:统计a[i]和b[i]  所有具有相同元素的个数  -   元素相同位置也相同的元素个数(A)  =   两个数组中元素相同位置不相同的个数(B)

 【有多少数字位置正确 (A),有多少数字在两个序列都出现过但位置不对(B)】

#include<stdio.h>
#define maxn 1010
int main() {
      int n, a[maxn], b[maxn];
        int kase = 0;
        while(scanf("%d", &n) == 1 && n) {
                //n=0时输入结束
                printf("Game %d:
", ++kase);
                for(int i = 0; i < n; i++)
                    scanf("%d", &a[i]);
                for(;;) {
                    int A = 0, B = 0;
                    for(int i = 0; i < n; i++) {
                                scanf("%d", &b[i]);
                                if(a[i] == b[i])
                                    A++;
                    }
                    if(b[0] == 0)
                        break; //正常的猜测序列不会有0,所以只判断第一个数是否为0即可
                    for(int d = 1; d <= 9; d++) {
                            int c1 = 0, c2 = 0; //统计数字d在答案序列和猜测序列中各出现多少次
                            for(int i = 0; i < n; i++) {
                                    if(a[i] == d) c1++;
                                    if(b[i] == d) c2++;
                            }
                    if(c1 < c2)   //统计的最小的个数 即为序列拥有相同的元素的个数
                        B += c1;
                    else B += c2;
                    }
                    printf("    (%d,%d)
", A, B-A);
                }

    }
    return 0;

}

 例题3-5 生成元

如果x加上x的各个数字之和得到y,就说x是y的生成元。给出n(1≤n≤100000),求最小
生成元。无解输出0。例如,n=216,121,2005时的解分别为198,0,1979。

分析思路:

假设所求生成元为m。不难发现m<n。换句话说,只需枚举所有的m<n,看看有没有哪个数是n的生成元。可惜这样做的效率并不高,因为每次计算一个n的生成元都需要枚举n-1个数。

只需一次性枚举100000内的所有正整数m,标记“m加上m的各个数字之和得到的数有一个生成元是m”,最后查表即可

#include<stdio.h>
#include<string.h>
#define maxn 100005
int ans[maxn];

int main() {
    int T, n;
    memset(ans, 0, sizeof(ans));
    for(int m = 1; m < maxn; m++) {
        int x = m, y = m;
        while(x > 0) {
                y += x % 10;
                x /= 10;
        }
        // y 就是 m + m上的各个位的数字之和
        if(ans[y] == 0 || m < ans[y])  //如果还没填(ans[y] = 0)或者 m比填入的值还要小
            ans[y] = m;
    }
    scanf("%d", &T);//几组数据
    while(T--) {
        scanf("%d", &n);
        printf("%d
", ans[n]);
    }
    return 0;
}

 例题3-6 

环状序列长度为n的环状串有n种表示法,分别为从某个位置开始顺时针得到。例如,图3-4的环状串有10种表示:

 CGAGTCAGCT,GAGTCAGCTC,AGTCAGCTCG等。在这些表示法中,字典序最小的称为"最小表示"。输入一个长度为n(n≤100)的环状DNA串(只包含A、C、G、T这4种字符)的一种表示法,你的任务是输出该环状串的最小表示。例如,CTCC的最小表示是CCCT,CGAGTCAGCT的最小表示为AGCTCGAGTC。

字典序,就是字符串在字典中的顺序。一般地,对于两个字符串,从第一个字符开始比较,当某一个位置的字符不同时,该位置字符较小的串字典序较小(例如,abc比bcd小);如果其中一个字符串已经没有更多字符,但另一个字符串还没结束,则较短的字符串的字典序较小(例如,hi比history小)。字典序的概念可以推广到任意序列,例如,序列1, 2, 4, 7比1, 2, 5小

#include<stdio.h>
#include<string.h>
#define manx 105

//比较 环状串S的表示法p是否比表示法ans的字典序小【长度是一样的,所以比较每个字典序的字符大小】
int less(const char* s,int p,int ans){
    int n = strlen(s);
    for(int i =0;i < n;i++){
        if(s[(p+i)%n] != s[(ans+i)%n])
            return s[(p+i)%n] < s[(ans+i)%n];
    }
    return 0;

}
    //假设输入的序列式 CGATCAGCT 第一次传入的是p=1,ans = 0,则比较
    //p表示法:CGATCATCT  <?> ans 表示法:GATCATCT的字典序大小
    //显然,第一位就不同,返回p表示法更小,即true
    //另ans = 1,始终使FLAG ans指向最小字典序的起始位置


int main(){
    int T;
    char s[manx];
    scanf("%d",&T);
    while(T--){
        scanf("%s",s);
        int ans = 0;//变量ans表示目前为止,字典序最小串在输入串中的起始位置,即[最小字符]
        int n = strlen(s);//即,找一个最小字典序的起始位置!
        for(int i = 1;i <n;i++)
            if(less(s,i,ans))
                ans = i;
        for(int i = 0;i<n;i++)
            putchar(s[(i+ans) %n]);
        putchar('
');
    }
    return 0;
}
原文地址:https://www.cnblogs.com/expedition/p/11485330.html