算法竞赛入门经典第二版-刘汝佳
算法竞赛入门经典第二版-刘汝佳
1.语言篇
1.程序设计入门
1.1算术表达式
整数值用%d输出,实数用%f输出。只要运算符的两边都是整数,则运算结果也会是整数。8/5的值是1,而不是1.6。8.0和5.0被看作是实数或浮点数,浮点数之间的运算结果是浮点数。
1.3顺序结构程序设计
算法竞赛是在比谁能更好地解决问题,而不是在比谁写的程序更高级。
第2章循环程序结构设计
2.1for循环
函数floor(x)返回不超过x的最大整数。四舍五入:floor(x+0.5)。
取正整数各个位上数的方法:a为两位正整数,a的十位是a/10,a的个位是a%10。
2.2 While循环和do while循环
循环次数不确定,或非递增式循环,适合while循环来实现。
先计算再判断,循环终止判断在计算之后,适合用do while循环。
int整数的大小:
-2^31~2^31-1。
2^31 = 2147483647。10位。
10^9只比2^31小一点,当数据取到最大10^9做一些乘法运算数据就会溢出。
long long数值型大小:
-2^63~2^63-1。输入格式为:%lld。
C语言中10^-6表示:1e-6。
2.3循环的代价
time.h头文件, Clock()函数返回程序目前为止运行的时间。该时间除以常数CLOCKS_PER_SEC得到以秒为单位的时间。
键盘输入的时间也会被计算在内。命令行下执行echo 20 | abc。操作系统会自动把20输入,其中abc是程序名。
--这个东西要在cmd中进入到abc.exe才行,好像不是很实用。
2.4 算法竞赛中的输入输出框架
输入次数不确定的输入语句://输入一些整数,输入一些整数,求出它们的最小值、最大值和平均值(保留3位小数)。
while(scanf("%d", &x) == 1) {};
用上述方式输入,Windows下输入完毕后按enter键,再按ctrl+z键,最后再按enter键即可结束输入。
如果读入的是整数,求平均要输出浮点数,不必声明一个浮点类型变量,计算sum/n强制转换成浮点类型就行。
(double)s / n
输入包含多组数据,每组数据第一行是整数个数n,第二行是n个整数。n=0为输入结束
标记,程序应当忽略这组数据。相邻两组数据之间应输出一个空行。这样的格式在算法竞赛中非常常见。
int x, n = 0, s = 0, max = -INF, min = INF, kase = 0; while (scanf("%d", &n) == 1 && n) {};
每组数据有多个变量while中的scanf有改动,几个变量就是scanf() == 几。
while (scanf("%lld %lld", &n, &m) == 2 && n && m)
#define INF 1000000后面不要加分号:
#include<stdio.h>
#define INF 1000000000
每次测试要手动输入许多数。一个好的方法,用文件把输入数据保存在文件中,输出数据也保存在文件中。
使用文件最简单的方法是使用输入输出重定向。在main函数入口加入以下两条语句,main函数进入后的前两行。
feropen(" input.txt", "r", stdin);
feropen(" output.txt", "w", stdout);
上述语句使得scanf从文件input.txt读入,printf写入文件output.txt。所有读键盘输入,写屏幕输出的函数都将改用文件。
本机测试使用文件重定向,提交到比赛就自动删除重定向语句。
#define LOCAL #include<stdio.h> int main() { #ifdef LOACL feropen(" input.txt", "r", stdin); feropen(" output.txt", "w", stdout); #endif // 功能语句 return 0; }
如果比赛要求用文件输出,但禁止用重定向方式。
int main() { FILE *fin, *fout; fin=fopen("data.in", "rd"); fout=fopen("data.out", "wd"); //--变量声明; while(scanf(fin, "%d", &x)==1) {}; fprintf(fout, "输出数据格式", 输出变量表列); fclose(fin); fclose(fout); return 0; }
截尾:
最后一位小数不四舍五入,进行截尾处理。保留c位小数截尾,公式:
res = floor( 数值 * 10^c ) / 10^c
指定精确到小数点后c位输出控制符:
//输出保留小数点后几数由输入c决定 printf("%.*f\n", c);
第3章数组和字符串
3.1数组
在main函数的外面声明数组时才可以开得很大。在main函数内,声明大数组就会异常退出。
比较大的数组应尽量声明在main函数外,否则程序可能无法运行。
复制数组元素到另外一个数组
从数组a复制k个元素到数组b,a,b都是整型数组:
memcpy(b,a,sizeof(int)* k)
数组a全部复制到b中:
memcpy(b,a,sizeof(a))
3-3蛇形数组
在n×n方阵里填入1,2,…,n×n,要求填成蛇形。
最外层while循环下标变量要小于n*n,里面四个while,判断条件为下一步的下标是否越界和下一步的元素是否被填充。
//tot是总共走的步数 while (tot < n * n) { while (x + 1 < n && !a[x + 1][y]) a[++x][y] = ++tot; ...... ...... ...... }
赋值后立刻作为数组下标是可以的:
a[x = 0][y = n - 1] = 1;
3.2字符数组
char *s 与 char s[]的区别:
char *s:s 是一个指针变量,存放在栈上;
字符串字面量 "abc" 存放在 只读常量区,不能修改内容。s 只是存了常量字符串首地址,指针变量可以随便改指向。
是一个字符数组,在栈上开辟一块连续内存,数组内容可以改,
s 是数组名,代表数组首元素地址,数组名是常量指针,不能被直接赋值修改指向。scanf("%s", s)会读入一个不含空格、tab和回车符的字符串,存入字符数组s。
程序3-4竖式问题
如果用拆解整数的方式来做这个题不知道会有多难了,用sprintf()函数把数字全部输出到字符数组,再用strchr来进行字符查找,就快很多了。
sprintf()函数
sprintf()函数把信息输出到字符串。用法:
//buf是char型数组char buf[99] sprintf(buf, "%d%d%d%d%d", abc, de, x, y, z);
strchr()函数的作用是在一个字符串中查找单个字符。
if(strchr(s, buf[i]) == null) ok = 0;
常用strchr()函数验证字符串s是否包含一组字符中所有字符。
3.3竞赛题目选讲
UVa 272,TEX Quotes 例题3-1,程序3-5
源码:https://vjudge.net/solution/70520971/ljtomz9GBqCnJfOgVIhr
getchar函数从标准输入读入一个字符。
本题采用边读边处理的方式:
while((c = getchar()) != EOF) { }
采用标志变量决定输出左引号还是右引号。
if语句"表达式版":a?b:c的含义是:当a为真时返回b值,否则返回c。
if (c == '"') { printf("%s", f ? "``" : "''"); f = !f; }
UVa10082,例题3-2 WERTYU
源码:https://vjudge.net/solution/70521284/gr8zT5X7bP44d2nbDlqQ
题目要把输入字符串按ASCII整体偏移按照题目描述输出。
定义常量数组无须指明大小,编译器会计算。
char s[] = "任意字符串";。
这题主要是定义了一个常量字符串s[]。for循环外定义了下标 i ,对于c=getchar(),找到c==s[i]便停下来,离开for循环。这其实是个空的for循环。
int i = 1; for (; s[i] && s[i] != c; i++); if (s[i]) printf("%c", s[i - 1]);
UVa401,例题3-3 回文词 Palindromes - UVA 401 - Virtual Judge
源码:https://vjudge.net/solution/70572675/7t0xpFmsf9W1O5HysUtk
ASCII码表中大写字母,小写字母和数字都是连续的。如果ch是大写字母,则ch-'A'就是它在字母表中的序号。
如果ch是数字,值ch-'0',就是这个数字的数值本身。
C语言中:
#include <ctype.h>
int isalpha(int c) //来判断c是否为大小写字母A-Z,a-z。注:c可以填char型的,函数参数是int型是因为ASCII中字母有对应int值。 int isdigit(int c) //判断字符是不是数字字符 '0' ~ '9'。 int isprint(int c) //判断是否为可打印字符。
这题的妙点在于:
从A到数字9构建一个36长度的镜像字符数组char *rev,没有镜像字符的为空格,利用ch-'A'或ch-'0'来查询镜像字符数组中是否有镜像字符。
利用isalpha函数来保证字符为字母。
检测回文和镜像串遍历它的一半长度即可。
消息字符串数组中有4条消息,两个标志变量,一个消息变量乘以2即可在数组中偏移2。在3、4条消息中选择。偏移我们在opengl中见过很多的。
例题3-4 猜数字游戏的提示 UVa 340 Master-Mind Hints - UVA 340 - Virtual Judge
实现一个经典"猜数字"游戏。给定答案序列和用户猜的序列,统计有多少数字位置正确(A),
有多少数字在两个序列都出现过但位置不对(B)。
输入包含多组数据。每组输入第一行为序列长度n,第二行是答案序列,接下来是若干
猜测序列。猜测序列全0时该组数据结束。n=0时输入结束。
源码:https://vjudge.net/solution/70932947/jjFqvRrtPb0ihPebOROy
//例题3-4 UVa340 //UVa340题目理解起来很难受
//b[]是猜测串,a是答案串 //对于A:在读入b[i]数组时,和a数组a[i]对比就行,有就A++。 //对于B:得在遍历a、b数组时,分别检查数字0-9每个数字在两数字串中出现的次数,让B每次加该数字出现的最小次数 //0-9检查完后,B得到的是在两串中每种数字出现的最小次数之和,A其实是每种数字在两串位置相同,数值相同次数之和。 //B-A就是在两串中都出现但位置不对等的个数 //难点:B为什么每次要加最小次数,在两串中都出现但位置不对等的个数肯定是以次数少的为准啊
例题3-5 生成元UVa1583 Digit Generator - UVA 1583 - Virtual Judge
源码:https://vjudge.net/solution/70934375/S17yNU0k4suAw59IMKrL
题目要找最小生成元。下述代码保证是最小的生成元。
//sum是生成数,i是生成元。 //当sum生成数没有生成元或当前生成元比之前sum的生成元小时,更新sum的生成元 if (a[sum] == 0 || i < a[sum]) a[sum] = i;
例题3-6 UVa1584 环状序列 Circular Sequence - UVA 1584 - Virtual Judge
源码:https://vjudge.net/solution/70945065/1rixRhe45AQFQDB0YBzk
字典序大小:两个字符串从前往后比较,同位置字母不同,顺序靠前或小写的字典序较小。同等情况下字符串较短的字典序较小。题目要求找环状串的最小字典序输出。只要找最小字典序串的字母在输入串中的位置。
这道题的核心代码:让机器模拟环状串的旋转。
int len = strlen(s); for (int i = 0; i < len; i++) { if (s[(p + i) % len] != s[(q + i) % len]) return s[(p + i) % len] < s[(q + i) % len]; }
思考题:找第二大的数
//思考题:找第二大的数,不需要用到数组。引入计数变量cnt讨论一下cnt为1、2和3以上的情况,维护好第一第二,不断更新它们即可 int x, cnt = 0, int first, second; while (scanf("%d", &x) != EOF) { cnt++; if (cnt == 1) { first = x; } if (cnt == 2) { if (x > first) { second = first; first = x; } else second = x; } else { if(x > first) { second = first; first = x; } if (x > second && x != first) second = x; } if (cnt > 1) printf("%d\n", second); } return 0;
习题3-2 分子量 UVa1586 Molar mass - UVA 1586 - Virtual Judge
源码:https://vjudge.net/solution/70967257/wp9FJ3pJPN469CrEm0Lf
//习题3-2 分子量 UVa1586 //答案的逻辑是:数字归数字一个判断,去更新数字,可以计算连续的数字字符。字母归字母一个判断。 //每次数字结束,就是字母,在判断下一个字母,更新pre之前,把上一部分的相对原子质量算掉,而且算的时候要把0的num变1.并且清零num。 //字符串跑完,再套公式:pre*num算一次。结尾如果是单个字母,num为0,依然要变成1。
习题3-4 周期串 UVa455 Periodic Strings - UVA 455 - Virtual Judge
源码:https://vjudge.net/solution/71004808/iqSVBXUE9sJXXvz7YucN
找字符串周期公式:预选周期p, 任意位置i都有s[i] = s[i % p]。和模拟环状字符串旋转有点像。外层循环是步长。其实内层循环基本没跑的。
for (int step = 1; step <= len; step ++) { //能被字符串长度整除的才算周期 if (len % step == 0) { int find = 1; //找字符串周期公式:预选周期p, 任意位置i都有s[i] = s[i % p] //里面这个循环没啥用,最多走1次,本质上每次在和s[0]作比较 for (int i = step; i < len; i ++) { //printf("i:%d\n", i); if (s[i] != s[i % step]) { find = 0; break; } } }
STL解决方法:s+s拼接,然后找出s第一次出现的位置。核心一行代码:(s + s).find(s, 1)。
习题3-5 谜题 UVa227 Puzzle - UVA 227 - Virtual Judge
源码:https://vjudge.net/solution/71031492/k4K2XI4Zknmk7j8PGNII
5*5格子里按照指令移动空白块。5行,有的行有空格。这里采用fgets(str, max, stdin)从标准输入(键盘)中读入一行。5行输入时会有换行符,所以每一行的长度包括了一个换行符。需要写个函数封装一下fgets(),并把倒数第二个字符'\n'换成'\0'。
char* stdin_get_str(char* str) { //fgets(buf, size, stream):从指定文件流读取一行, //size:预设的最大读取字符上限 //stream:流,一般是标准输入流,写stdin fgets(str, maxn, stdin); int len = strlen(str); //fgets读入一行时,计算长度会计算结尾的'\n' if (str[len-1] == '\n') str[len-1] = '\0'; return str; }
对于走格子场景,走了后必须先检验走完后是否在区域内,再做事情。写个check()函数。if check(point) {...}。
这道题目使用了结构体
struct Point { int x, y; }blank, square;
写函数时,对于类或结构体,尽量使用传引用的方式,加const禁修改:
bool check(const Point &p){ }
习题3 - 7 DNA序列 UVa1368 DNA Consensus String - UVA 1368 - Virtual Judge
//题意:输出到m个序列的Hamming距离之和最小的DNA序列,以及对应的距离 //根据hamming距离定义,如果有个字符串和每个字符串都很相似,即不同字母个数越少 //那么该字符串离所有的字符串的hamming距离之和就越小 //对于没有空格的字符串(正常scanf读),构造一个二维数组int cnt[maxn][4],统计每个位置ACGT出现的次数, //用map<char, int> char_int, cnt[j][ char_int[s[i][j] ]++ 来完成计算。 //然后再扫cnt数组找每个位置出现次数最多数,保留下标,去int_char字符串映射成字母,存入ans串内 //对于多组输入,每组输入应该要用memset()清除掉所有有痕迹的数组,以便于给下一组输入用
有意思的是这个题目的hamming距离的定义,感觉面试拿来考察不错。代码:https://vjudge.net/solution/71038441/Q7okpdkxoQPXq30IhYK7
习题3-8 循环小数 UVa202 Repeating Decimals - UVA 202 - Virtual Judge
//习题3-8 循环小数 UVa202 //当前小数 dic = a / b,余数:r = a % b, origin_a = a, a = a*10; a乘10之后再重复上述操作 // 出现相同余数了,循环节就找到了 //用数组pos[r] = n记录每个余数第一次出现的下标位置; //逐位求小数位digit[n] = dic,每次检查余数是否曾经出现过bool r_exit[r]
这个题目有点意思。源码:https://vjudge.net/solution/71170385/HQEmMTu6NNKTqggOZxpx
习题3-9 子序列(All in All, UVa 10340)All in All - UVA 10340 - Virtual Judge
这道题没说输入字符串最长多少,没说就是最大情况100w,得开100w char数组,不然Runtime Error。参考答案就是i, j丢while外面,while(i<s1.len1, j<s2.len2)。s1[i] == s2[j],i ++ , j ++ 。else j ++ 。如果i能走到len1,就输出yes。是到简单题。

浙公网安备 33010602011771号