蓝桥杯备考——算法竞赛入门经典(第2版)学习笔记3

算法竞赛入门经典(第2版)学习笔记3

第3章 数组和字符串

3.1 数组

注1:在算法竞赛中,常常难以精确计算出需要的数组大小,数组一般会声明的稍大一些。在空间够用的前提下,浪费一点不会有太大影响。

注2:对于变量n,n++和++n都会给n加1,但当它们用在一个表达式中时,行为有所差别:n++会使用加1前的值计算表达式,而++n会使用加1后的值计算表达式。可以借助之前的例题:多组数据输入,输出case1···,case2···此类的结果时所运用到的输出++count帮助理解。

注3:比较大的数组应尽量声明在main函数外,否则程序可能无法运行。

注4:数组不能进行赋值操作:如果声明 “int a[max],b[max]”,是不能赋值b=a的。如果要从数组a赋值k个元素到数组b,可以这样做:memcpy(b,a,sizeof(int) *k)。当然,如果数组a和b都是浮点型的,复制时要写成 “memcpy(b,a,sizeof(double) *k)”。另外,在使用memcpy函数时要包含头文件string.h。如果要把数组a全部复制到数组b中,可以写的简单一些:memcpy(b,a,sizeof(a))。

开灯问题
有n盏灯,编号为1~n。第1个人把所有灯都打开,第2个人按下所有编号为2的倍数的开关(这些灯将被关掉),第3个人按下所有编号为3的倍数的开关(其中关掉的灯将被打开,开着的灯将被关闭),以此类推。一共有k个人,问最后有哪些灯开着?
输入n和k,输出开着的灯的编号。k<=n<=1000。
样例输入:
7 3
样例输出:
1 5 6 7

分析:用a[1],a[2],a[3],…,a[n]表示编号为1,2,3,…,n的灯是否开着。

程序3-2 开灯问题

#include<stdio.h>
#include<string.h>
#define max 1004
int main(){
 int n,k,a[max];
 memset(a,0,sizeof(a)); //将数组a清零 
 scanf("%d%d",&n,&k);
 for(int i=1;i<=k;i++)
  for(int j=1;j<=n;j++){
   if(j%i==0){
   a[j]=!a[j];
   }
  }
 for(int j=1;j<=n;j++){
  if(a[j]==1)
  printf("%d ",j);
 }
}
  • memset(a,0,sizeof(a)); 这句话作用是将数组a进行清零,要在string.h中定义。
  • 注意取反的方法

蛇形填数问题
在n×n方阵里填入1,2,···,n×n,要求填成蛇形。n<=8

分析:

  • 类比数学中的矩形,可以用一个二维数组来存储方阵。
  • 每个数字的位置可以用坐标来表示。如一开始1的坐标为(0,n-1),x=0,y=n-1。
  • 而从1开始的轨迹是:下下下左左左上上上右右下下左上。
  • 总之是先向下,然后到不能填为止,再向左,接着又向上,最后是右。(抓住轨迹的特点是关键
  • 不能填是指:再走就出界或者再走就要走到填过的格子里。
  • 要初始化哦

程序3-3 蛇形填数

#include<stdio.h>
#include<string.h>
#define max 10
int main(){
 int n,x,y,a[max][max],t=0;//x,y代表数字的坐标 
 scanf("%d",&n);
 memset(a,0,sizeof(a));//把数组清零 
 t=a[x=0][y=n-1]=1;    //数字1的位置 
 while(t<n*n){ 
  while(x+1<n && !a[x+1][y]) a[++x][y]=++t; //向下 
  while(y-1>=0 && !a[x][y-1]) a[x][--y]=++t; //向左 
  while(x-1>=0 && !a[x-1][y]) a[--x][y]=++t; //向上 
  while(y+1<n && !a[x][y+1]) a[x][++y]=++t; //向右 
 } 
 for(x=0;x<n;x++){
  for(y=0;y<n;y++)
  printf("%3d",a[x][y]);
  printf("\n");
 }
}

运行截图:
在这里插入图片描述

注:原则是:先判断,再移动,而不是走一步之后发现越界了再退回来。以第一个while判断语句举例:越界只需要判断x+1<n;下一个格子是(x+1,y),因此只需“a[x+1][y] == 0”

3.2 字符数组

注1:C语言中的字符型用关键字char表示,它实际存储的是字符的ASCII码。字符常量可以用单引号法表示。在语法上可以把字符当作int型使用。
注2:scanf("%s",s),不要在s前面加上“&”符号。它会读入一个不含空格、TAB和回车符的字符串,存入字符数组s。如果是字符串数组char s[maxn][maxl],可以用scanf("%s",s[i]),读取第i个字符串。注意,scanf("%s",s)遇到空白字符会停下来。
注3:%5d,表示按照5位数打印,不足5位就在前面补空格。(%03d)

竖式问题
找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的数字集合。输入数字集合(相邻数字之间没有空格),输出所有竖式。每个竖式前应有编号,之后应有一个空行。最后输出解的总数。为了便于观察,用小数点显示,实际输出应该输出空格,不是小数点。
样例输入:
2357
样例输出:
<1>
。。775
×。。33
————
。 2325
2325 。
————
2 5 5 7 5
(空格)
The number of solutions =1

分析:

  • 尝试所有的abc和de,判断是否满足条件。

  • 有两个问题要进行考虑:判断abc * de是否是个合法的竖式,以及打印输出问题。先考虑输出问题,每个竖式需要打印7行,但不一定要用7条printf语句,1条就够了。首先计算第一行的乘积x=abc * e,然后是第二行y=abc * d,最后是总的乘积结果z=abc * de,然后再打印出来。
    得到printf("%5d\nX%4d\n------\n%5d\n%4d\n------\n%5d\n\n",abc,de,x,y,z);

程序3-4 竖式问题

#include<stdio.h>
#include<string.h>
int main(){
 int count=0;
 char s[20],buf[99];
 scanf("%s",s);
 for(int abc=100;abc<=999;abc++)
  for(int de=10;de<=99;de++){
   int x=abc*(de%10);
   int y=abc*(de/10);
   int z=abc*de;
   sprintf(buf,"%d%d%d%d%d",abc,de,x,y,z);//把这些数都存到buf里 
   int ok=1;
   for(int i=0;i<strlen(buf);i++)
    if(strchr(s,buf[i])==NULL) ok=0; //将缓冲区的每个数字与输入的数字序列进行比较,若不存在,则返回NULL 
   if(ok){
    printf("<%d>\n",++count);
    printf("%5d\nX%4d\n------\n%5d\n%4d\n------\n%5d\n\n",abc,de,x,y,z);
   } 
  }
  printf("The number of solutions = %d\n",count); 
} 

运行截图:
在这里插入图片描述

  • 关于buf[i]的含义:关于这个buf数组,我想了好久,我一直以为是123,45,2222,3333这样的,但其实不是,这是一个字符数组,每一个数字就是一个字符,也就是1234522223333这样子的,于是才可以在s中搜索buf[i]
  • strchr函数的作用:在一个字符串中查找单个字符。要加头文件string.h。C 库函数 char *strchr(const char *str, int c) 在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。str – 要被检索的 C 字符串。c – 在 str 中要搜索的字符。返回值:该函数返回在字符串 str 中第一次出现字符 c 的位置,如果未找到该字符则返回 NULL。
  • sprintf是输出到字符串,printf输出到屏幕,fprintf输出到文件。多数情况下,屏幕总是可以输出的,文件一般也能写(除非磁盘满或者硬件损坏),但字符串就不一定了:应该保证写入的字符串有足够的空间。怎么才算是足够的空间呢?答案是字符个数加1,因为c语言的字符串是以空字符“\0”结尾的
  • strlen(s)函数的作用是获取字符串s的实际长度,如果输入是“2357”,那么实际上s只保存了5个字符(不要忘记还有一个结束标记\0)。strlen(s)返回的就是结束标记之前的字符个数。因此这个字符串中的各个字符依次是s[0],s[1],···.s[strlen(s)-1],而s[strlen(s)]正是结束标记“\0”
  • 由于字符串的本质是数组,所以只能用strcpy(a,b),strcmp(a,b),strcat(a,b)来执行赋值,比较,连接的操作,上述函数要在string.h中声明

3.3 竞赛题目选讲

例题3-1 TeX中的引号

在这里插入图片描述

分析:本题的关键在于,如何判断一个双引号是左双引号还是右双引号。方法就是:使用一个标志变量即可。
但还有另外一个问题是:如何输入这样的带空格的字符串。
第一种方法
使用“fgetc(fin)”,它读取一个打开的文件fin,读取一个字符,然后返回一个int值。为什么返回的是int值而不是一个char呢?因为如果文件结束,fgetc将返回一个特殊标记EOF,它并不是一个char。如果把fgetc(fin)的返回值强制转换成char,将无法把特殊的EOF和普通字符区分开。如果要从标准输入读取一个字符,可以用getchar,它等价于fgetc(stdin)。

第二种方法
使用“fgets(buf,maxn,fin)”读取完整的一行,其中buf的声明为char buf[maxn]。将读取完整的一行放在字符数组buf中。应当保证buf足够存放下文件的一行内容。除了文件结束前没有遇到“\n”这种特殊情况外,buf总是以“\n”结尾。当一个字符都没有读到时,fgets返回NULL。
注1:C语言中的gets(s)存在缓冲区溢出漏洞,不推荐使用。在C11标准里,该函数已被正式删除

程序3-5 TeX中的引号

#include<stdio.h>
int main(){
 int c,q=1;
 while((c = getchar()) !=EOF){
  if(c == '"'){
   printf("%s",q?"``":"''");
   q=!q;
  }
  else printf("%c",c); 
 }
}
  • q为一个代表左右引号的标志,q=1的时候是左引号,q=0时是右引号。
  • 区分%s和%c: %c格式对应的是单个字符,%s格式对应的是字符串。
  • 注意a?b:c的用法,if语句的“表达式版”
  • 本题的特点是:可以边读边处理,而不需要把输入的字符串完整的存下来,因此getchar是一个不错的选择。

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

分析:
此题和例题3-1一样,每输入一个字符,都可以直接输出一个字符,因此getchar时输入的理想方法。问题在于:如何进行这样的输入输出变换呢?一个较好的方法是使用常量数组。

程序3-6 WERTYU

#include<stdio.h>
char s[]="`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";
int main(){
 int c,i;
 while((c=getchar())!=EOF){
  for(i=1;s[i] && s[i]!=c;i++);//单纯做循环,为了得到i 的值。找到错位之后的字符在常量表中的位置
  if(s[i]) putchar(s[i-1]);//如果找到,则输出它的前一个字符
  else putchar(c);
 }
 return 0;
}
  • 在常量数组c中为什么有 \ \ 这“两个”字符呢?
    实际上我们应该想到转义字符这个概念。不只是在C语言中,在Java、C++中也同样,用 \\ 这个字符(没错,它是一个字符)来表示真正意义上的反斜线即 \ 。
  • 注意getchar的用法
  • C 库函数 int putchar(int char) 把参数 char 指定的字符(一个无符号字符)写入到标准输出 stdout 中。
  • for(i=1;s[i] && s[i]!=c;i++);
    这个循环的在代码中作用,用来找到输入当前字母的位置,找到得到 i, 用于下面的操作。即单纯做循环,不做具体操作。for循环后加分号,表示这个循环是空语句,除了执行for()括号里的,什么都不干,然后就执行下一行。

注2:善用常量数组往往能简化代码。定义常量数组时无需指明大小,编译器会计算。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值