算法竞赛入门(3)学习笔记——数组和字符串


本节中,主要讲解

一:数组

1.1【数组逆序输出】

#include<stdio.h>
#define maxn 105
int a[maxn];
int main()
 {
	int x, n=0;
	while (scanf("%d", &x) == 1)
		a[n++] = x;
	for (int i = n - 1; i >= 1; i--)
		printf("%d ", a[i]);
	printf("%d\n", a[0]);//保证输出的行尾没有空格。
	return 0;
}

反思
1.定义数组长度为105是为了安全起见,将空间稍微大于理论空间。千万记得define后面不能加分号!!!!!!!!!

2数组只输入了n-1个数字,因为输入循环时,第n-1个输入后,n++,然后退出了循环。

3.n++使用+1之前的值进行运算,++n使用+1之后的值进行运算。

4.数组定义放在main函数之外,才可以开很大的数组。否则,放在main中很可能会出现异常而退出。

5.数组的复制操作:
将数组a的k个元素复制到数组b中:memcpy(b,a,sizeof(int)*k);
浮点型将int改为float即可;
在文件开头要加上#include<string.h>;
如果是全员复制,则只需简写memcpy(b,a,sizeof(a))

6.更改代码字体:在工具→选项→环境→字体和颜色中更改。

7.将最后一个输出单独拿出来,保证最后一个输出后面没有空格。

8.输入完后,想退出输入循环:Ctrl+z

1.2【开灯问题】

有k个人(k≤n≤1000)和n盏灯(编号1~n)。第一个人将所有的灯打开,第二个人按下倍数为2的开关(这些灯将被关上),第三个人按下倍数为3的开关(开的灯将被关上,关上的灯将被打开)。。。依次类推。最后哪些灯还打开?
样例输出:
3 7
样例输出:
1 5 6 7

#include<stdio.h>
#include<string.h>
#define maxn 1010
int a[maxn];
int main() 
{
	int k, n, first = 1;
	memset(a, 0, sizeof(0));//数组全部置0,需要string头文件。也可循环进行操作。
	scanf("%d%d", &k, &n);
	for(int i=1;i<=k; i++)
		for (int j = 1; j <= n; j++) 
		{
			if (j % i == 0) a[j] = !a[j];//数组置反
		}
	for (int i = 1; i <= n; i++) 
		if (a[i])
		 {
			if (first) first = 0;//判断开头,不需要空格
			else
				printf(" ");
				printf("%d", i);
		}
	printf("\n");
	return 0;
}

自写出错点:
始终要记得,define后面没有分号,没有分号!!!

1.3【蛇形填数】

在nxn方阵里填入1,2,…,n*n,要求填成蛇形。
例如n=4时方陈为:(空格不必严格输出,n<=8)
10 11 12 1
9 16 13 2
8 15 14 3
7 6 5 4

#include<stdio.h>
#include<string.h>
#define maxn 20
int a[maxn][maxn];
int main() 
{
	int n, x, y, tot = 0;
	memset(a, 0, sizeof(a));
	scanf("%d", &n);
	tot = a[x = 0][y = n - 1] = 1;
	while (tot < n * n)
	 {
		while (x + 1 < n && !a[x + 1][y]) a[++x][y] = ++tot;//下
		while (y - 1 >= 0 && !a[x][y - 1]) a[x][--y] = ++tot;//左
		while (x - 1 >= 0 && !a[x - 1][y]) a[--x][y] = ++tot;//上
		while (y + 1 < n && !a[x][y + 1]) a[x][++y] = ++tot;//右
	}
	for (x = 0; x < n; x++)
	{
		for (y = 0; y < n; y++)
			printf("%3d", a[x][y]);
		printf("\n");
	}
	return 0;
}

注意
本题细节太多,稍有不慎就无法完成。
上面写法是精简美观版本,有很多操作在一步写法中就完成了。

二:字符数组

2.1【竖式问题】

找出所有形如abc*de(三位数乘以两位数)的算式,使得在完整的竖式中,所有数字都属于一个特定的数字集合。输入数字集合(相邻数字之间没有空格),输出所有竖式。每个竖式前应有编号,之后应有一个空行。最后输出解的总数。具体格式见样例输出(为了便于观察,竖式中的空格改用小数点显示,但你的程序应该输出空格,而非小数点)。
样例输入:2357

样例输出:

<1>

…775

X…33


.2325


25575

The number of solutions = 1

#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);
			int ok = 1;
			for (int i = 0; i < strlen(buf); i++)
				if (strchr(s, buf[i]) == NULL) ok = 0;
			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);
	return 0;
}

注意
1.C语言中允许直接的方法表示字符,例如“a”代表的就是a的ASCII码。
2.转义序列:
回车符“\n”
空字符“\0”也是C语言字符串结束的标志
单引号“’ ”
双引号“" ”
3.C语言中字符型的关键字:char,实际存储的字符的ASCII码。字符常量可以用单引号法表示。语法上可将字符当作int型使用。
4.scanf("%s",s) 中间没有使用&,遇到空字符会停下来。
5.字符串数组char s[maxn][maxm],可以使用scanf("%s",s[i])读取第i个字符串。
6.printf使用一条语句即可输出,不必使用7条。 %5d表示按照5位数打印,不足5位在前面补空格。
7.strchr:在一个字符串中查找单个字符。
8.sprintf:把信息输出到字符串,应保证字符串足够大,可以容纳输出信息。
9.字符串足够大:每个字符串以空字符“\0”结束,所以最小为字符串+1,所以在空间足够大时,可以适当浪费一点空间。
10.strlens(s),获取字符串s的实际长度。例如s[20]存4253,实际长度为5,其余的无意义数,strlen获取的是“\0”标记及之前的,各个字符为s[1]…s[strlen-1]
11.字符串的赋值、比较、连接操作,使用strcpy(a,b)、strcmp(a,b)strcat(a,b),上述函数定义在string.h中。

自写出错
要注意for循环的范围,别把不能循环的放进去了。

三:竞赛题目选讲

3.1【Tex中的引号】

【题目描述】
在TeX中,左双引号是“``”,右双引号是“''”。输入一篇包含双引号的文章,你的任务是
把它转换成TeX的格式。
样例输入:
"To be or not to be," quoth the Bard, "that
is the question".
样例输出:
``To be or not to be,'' quoth the Bard, ``that
is the question''.
#include<stdio.h>
int main() 
{
	int c, q = 1;
	while ((c = getchar()) != EOF) 
	{
		if (c == '"') 
		{
			printf("%s", q ? "``" : "''");
			q = !q;
		}
		else
			printf("%c", c);
	}
	return 0;
}

思考:
一:输入字符串
1.scanf("%s")可以输入字符,但是遇到空格/Tab键会停止。
2.fgetc(fine)可以读取一个打开的文件fin,读取一个字符,然后返回一个int值。在文件结束时,fgetc返回一个特殊标记EOF(非char)。
3.从标准输入读取一个字符使用getchar,等价于fgetc(stdin)。可以看下面的详细讲解:关于C语言中getchar()的详细使用
4.fgets(buf,maxn,fin),读取完整的一行,其中buf表示 char buf[maxn]。该函数读取不超过maxn-1个字符,在末尾添上结束符\0。该函数读取到回车符\n,就会停止,然后加上\0.只有读取到当文件结束符,并且文件的最后一个不以\n结束时,buf才会不以\n结束。当一个字符都没有读到时,fgets返回NULL。
5.gets是fgets的标准输入版本,但是由于gets(s)没有指明读取的最大字符数,所有gets不停往s中存。gets存在缓冲区溢出漏洞,目前C++已废除。
6.?:是if的表达式版。

3.2【WERTYU】

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


3.3【回文词】

输入一个字符串,判断它是否为回文以及镜像串。输入字符串保证不含数字0.所谓回文串,就是反转之后原串相同,如abba和madam。所谓镜像串,就是左右镜像之后和原串相同,如2S和3AIAE。注意,并不是每个字符在镜像之后都能得到一个合法字符,本题中,每个字符的镜像如下所示,(空白项表示该字符镜像后不能得到一个合法的字符)。
回文词


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)


3.5【生成元】

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


3.6【环状串】

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

环状串

CGAGTCAGCT,GAGTCAGCTC,AGTCAGCTCG等。

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

例如,CTCC的最小表示是 CCCT,CGAGTCAGCT的最小表示为AGCTCGAGTC。


四:注解与习题

例题: 数据统计
输入一些整数,求出它们的最小值、最大值、平均值(保留3位小数)。输入保证这些数都是不超过1000的整数。
样例输入:
2 8 3 5 1 7 3 6
样例输出:
1 8 4.375

#include<stdio.h>
int main()
 {
	const int INF = 100000000;//INF(infinite,无限的)
	int x, n=0, max = -INF,min = INF, s=0;
	while (scanf("%d", &x) == 1) 
	{
		s += x;
		if (max < x)max = x;
		if (min > x)min = x;
		n++;
	}
	printf("%d %d %.3f\n", min, max, (double)s / n);
	return 0;
}

反思:
1.通过scanf("%d", &x)返回的是成功输入的变量的个数。当输入结束时,scanf无法再次读取x,返回0.
2.Windows系统下,输入结束后:按Enter,再按Ctrl+Z,最后按Enter结束输入。 Linux系统下,按Ctrl+D结束输入。
3.浮点计算在前面加上(double)即可。
4.max和min要制定初值,否则机器会开始随机生成数,结果报错。本处使用常量来,min初值要大,max初值要小。但问题:INF数据不够大或者运算溢出。

进阶:
暴露问题:输入要逐行输入,输出时一卷屏前面的就看不见,且与标准答案比较不方便。
解决办法:通过文件。
实际操作:输入输出重定向,在main函数的入口加上

freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);

注意事项:有的比赛不能使用文件,或者不能使用重定向方式读取文件,看请比赛要求是否是标准输入输出。并且输入输出文件名要仔细,不要使用相对路径或者绝对路径。
自我测试完成后删除重定向语句:

#define LOCAL
#include<stdio.h>
#define INF 1000000
int main() 
{
#ifdef LOCAL
	freopen("input.txt", "r", stdin);
	freopen("output.txt", "w", stdout);
#endif
	int x, n = 0, max = -INF, min = INF, s = 0;
	while (scanf("%d", &x) == 1)
	 {
		s += x;
		if (max < x)max = x;
		if (min > x)min = x;
		/*
			printf("%d %d %.3f\n", min, max, (double)s / n);
		*/
		n++;
	}
	printf("%d %d %.3f\n", min, max, (double)s / n);
	return 0;
}

注意:
1.重定向部分写在#ifdef和#endif中。表示只有定义了符号LOCAL,才编译中间的两条。上程序首部就定义了LOCAL,提交时删除即可。或者在编译选项中定义LOCAL。
2.printf在/* */中可以用来中间检测输出。
非重定向版:

#include<stdio.h>
#define INF 1000000
int main() 
{
FILE* fin, * fout;
fin=fopen("date.in", "rb");
fout=fopen("date.out", "wb");
int x, n = 0, max = -INF, min = INF, s = 0;
while (fscanf(fin,"%d",&x) == 1)
{
s += x;
if (max < x)max = x;
if (min > x)min = x;
n++;
}
fprintf(fout,"%d %d %.3f\n", min, max, (double)s / n);
fclose(fin);
fclose(fout);
/*声明变量fin和fout,把scanf换成fscanf
第一参数为fin;printf换成fprintf,第一参数为fout
最后执行fclose,关闭两个文件*/
	return 0;
}

思考:
1.重定向文件更简洁,但不能同时文件读写和标准输入输出。
fopen写法繁琐,可以重复打开读写文件。将上文件改为标准输入/出,只需要赋值:fin=stdin;fout=stdout;同时不调用fopen和fclose

例题: 数据统计
输入一些整数,求出它们的最小值、最大值、平均值(保留3位小数)。输入保证这些数都是不超过1000的整数。
输入包含多组数据,每组数据的第一行是整数个数n,第二行是n个整数。n=0为输入结束标志,程序忽略此数组。相邻两组数据之间应输出一个空行。
样例输入:
8
2 8 3 5 1 7 3 6
4
-4 6 10 0
0
样例输出:
Case 1:1 8 4.375
Case 2:-4 10 3.000

#include<stdio.h>
#define INF 1000000
int main() 
{
	int n=0, s = 0,x,kase=0;
	while (scanf("%d", &n) == 1 && n)
	 {
		int s = 0 ,max = -INF, min = INF ;
		for (int i = 0; i < n; i++)
		 {
			scanf("%d", &x);
			s += x;
			if (x < min)min = x;
			if (x > max)max = x;
		}
		if (kase)printf("\n");
		printf("Case %d: %d %d %.3f\n", ++kase, min, max, (double)s / n);
	}
	return 0;
}

反思:
1.输入循环:在n=0作为输入标记并仍然判断scanf的返回值,是增加程序的鲁棒性。
2.kase的使用:kase为数据编号计数器。当输入第二行数据时,会空一行。
3.min和max放置于循环内:在进行下一组运算时能够重置,否则将会影响下一组的结果。

五:习题

5.1 水仙花数

题目:
输出100~999中的所有水仙花数。若三位数ABC满足ABC=A3+B3+C3,则称其为水仙花数,例如153=13+53+33

#include<stdio.h>
int main() 
{
	for (int i = 100; i <= 999; i++) 
	{
		int k, l, m;
		k = i / 100;
		l = i / 10 % 10;
		m = i % 10;
		if (i == k * k * k + l * l * l + m * m * m)
			printf ("%d\n",i);
	}
	return 0;
}

思考:
本题中分出每位的数字很重要,%和/的运算要清楚。

5.2 韩信点兵

题目:相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入3个非负整数a,b,c ,表示每种队形排尾的人数(a<3,b<5,c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100 。
样例输入
2 1 6
2 1 3
样例输出
Case 1:41
Case 2:No answer

#include<stdio.h>
int main() 
{
	int a, b, c,kase=0;//计数变量
	scanf("%d%d%d", &a, &b, &c);
	for (int i = 2; i <= 19; i++) 
	{
		int n = 5 * i + b;
		if ((n - a) % 3 == 0 && (n - c) % 7 == 0)
		 {
			printf("Case%d :%d\n",++kase, n);
		}
	}
	if (kase == 0)printf("No Answer");
	return 0;
}

反思:
第一次卡壳:未能找到3、5、7的表示。
第二次卡壳:no answer这个条件语句不知道怎么放置,且没有使用计数变量进行输出。

5.3 倒三角形

输入正整数n<=20,输出一个n层的倒三角形。例如,n=5时输出如下:

#########
 #######
  #####
   ###
    #
#include<stdio.h>
int main()
 {
	int n;
	scanf("%d", &n);
	for (int i = n; i >= 1; i--)
	 {
		for (int j = n - i;j>0;j--) 
		{
			printf(" ");
		}
		for (int k = 2 * i - 1; k > 0; k--)
		 {
			printf("#");
		}
		printf("\n");
	}
	return 0;
}

反思:
本题需要深入理解遍历原理和顺序执行规则。

5.4 子序列的和

输入两个正整数n<m<106 ,输出1/n2+ 1/(n+1)2+…+1/m2,保留5位小数。输入包含多组数据,结束标记为n=m=0。提示:本题有陷阱。
样例输入:
2 4
65536 655360
0 0

样例输出:
Case 1: 0.42361
Case 2: 0.00001

5.4 分数化小数

输入正整数a, b, c,输出a/b的小数形式,精确到小数点后c位。a, b <= 106,c <= 100。输入包含多组数据,结束标记为a=b=c=0.

样例输入:
1 6 4
0 0 0

样例输出:

Case 1:0.1667

5.5 排列

用1,2,3…9组成3个三位数abc, def, ghi, 每个数字恰好使用一次,要求:abc:def:ghi=1:2:3,按照“abc def ghi”的格式输出所有解,每行一个解。

5.6 思考题

六:笔者注释

本文为学习《算法竞赛入门经典(第2版)》所做的学习笔记,仅作学习交流使用,切勿用作他途!如有侵权,联系立删。本文内容可能会有理解错误或遗漏,望乞指正,不胜感激!

七: 参考资料

刘汝佳. 算法竞赛入门经典[M]. 第2版. 北京 : 清华大学出版社, 2014

©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页