来自于清北学堂的导学部分,对其中的部分题目做简单讲解
一、基础算法(枚举、模拟、贪心、分治)
【简介】
枚举、模拟、贪心、分治是最基础也是最需要打好基础的几个算法,这些题目普遍分布在NOIP 的前两题,代码难度较低,学好这些算法不但是拿到 NOIP 保底分的保障, 同时也能够帮助你更好的分析部分综合题。
不能因为这一块的相对简单而忽视了他们的重要性,相反的,选手的目标并不局限 于掌握算法与会做题目上,更要同时借这个机会提升自己的思考问题的速度、对题目的敏感程度、写代码的速度、调试程序的能力等多维代码能力,为赛场上的代码效率打下牢固的基础。
【学习要点】
1. 对于枚举和模拟,一定要多写多练,锻炼自己写代码的速度和正确性,保证用最快的时间完成正确的代码。
2. 贪心题要靠一定题量的积累,选手通过掌握一些贪心的定式达到举一反三的效果。
3. 需要掌握基本分治思想、同时准备自己的二分和三分模板,做到写二分这种题时形成一种套路和定式,减少自己的出错概率。
4. 养成一个良好的读题习惯,题目中的每句话都可能十分关键,尤其是题目结尾样例数据和数据范围约定以及可能存在的注释部分,更是尤为重要。
5. 养成一个良好的代码习惯,首先确认算法的正确性,再理清楚代码的逻辑结构, 最后再写代码,急于动手一般会导致你浪费更多的时间在写代码中途的迷茫和写完后永无止境的调试中。
【学习难点】
1. 这些题虽然简单,但这类题经常有一个很糟糕的共性——题目很难完全表述清楚,所以题目文字叙述会很长很长,题目本身的长度往往干扰到选手本身的做题心态, 所以一定要锻炼自己的读题能力,不惧怕读长题,学会从长题中逐渐提炼出题目最根本的意思。
2. 贪心题的难点在于贪心算法本身的正确性,即大胆猜想,小心求证,在寻找规律时一定要足够大胆,但是找到的规律本身一定要符合逻辑,在面对自己提出的算法时, 应从两个角度去考虑:
i) 尝试为自己的算法找一个合逻辑的解释;
ii) 反复寻找可能存在的反例去反向验证。而掌握大量的套路,可以快速看出题目的套路来源,就能很快找到一个合理逻辑。
3. 对分治题的要求体现在两点:
i) 能正确的写出分治代码,对分治算法的框架有一个清晰的掌握并有一个漂亮的实现;
ii) 分治题一般都有很明显的特征,通过题目的积累能慢慢对分治的题型有个大致了解,在遇到新的分治题的时候能迅速意识到此题的算法应该是一个分治,帮助你有一个正确的思考导向,题目就已经完成了一半(在二分题上尤为明显)
【例题】
1. 蛇形矩阵(CODEVS 1160)
2. 均分纸牌(2002年NOIP提高组)(CODEVS 1098)
3. 国王游戏(2013年NOIP提高组)
4. 跳石头(2015年NOIP提高组)
5. BalancedLineup(POJ 3264)
6. KD 之死(BZOJ 1555)
7. Kieszonkowe(BZOJ4291)
8. Well (BZOJ 2792)
codevs.cn, http://codevs.cn/problem/1160/
tyvj.cn
poj.org
1. 1160 蛇形矩阵
题目描述 Description
小明玩一个数字游戏,取个n行n列数字矩阵(其中n为不超过100的奇数),数字的填补方法为:在矩阵中心从1开始以逆时针方向绕行,逐圈扩大,直到n行n列填满数字,请输出该n行n列正方形矩阵以及其的对角线数字之和。
输入描述 Input Description
n(即n行n列)
输出描述 Output Description
n+1行,n行为组成的矩阵,最后一行为对角线数字之和
样例输入 Sample Input
3
样例输出 Sample Output
5 4 3
6 1 2
7 8 9
25
算法分析:
可以采用模拟法,模拟从1开始的数字逆时针旋转的过程,整个过程可以看作为:
1) 确定矩阵中心数字1开始的坐标
x=y=(n+1)/2;
a[x][y]=k=1;
2) 确定转的圈数
for(i=1;i<=n/2;i++)
3) 模拟逆时针转一圈的过程
向右走一个: a[x][++y]=++k;
向上走: for(j=1;j<2*i;j++) a[--x][y]=++k;
向左走: for(j=1;j<=2*i;j++)a[x][--y]=++k;
向下走: for(j=1;j<=2*i;j++)a[++x][y]=++k;
向右走: for(j=1;j<=2*i;j++)a[x][++y]=++k;
本题解法较多,可以尝试一下其他的解法。
2. 均分纸牌(2002 年 NOIP提高组)(CODEVS 1098)
[问题描述]
有 N 堆纸牌,编号分别为 1,2,…, N。每堆上有若干张,但纸牌总数必为 N 的倍数。可以在任一堆上取若干张纸牌,然后移动。
移牌规则为:在编号为 1 堆上取的纸牌,只能移到编号为 2 的堆上;在编号为 N 的堆上取的纸牌,只能移到编号为 N-1 的堆上;其他堆上取的纸牌,可以移到相邻左边或右边的堆上。
现在要求找出一种移动方法,用最少的移动次数使每堆上纸牌数都一样多。
例如 N=4,4 堆纸牌数分别为:
① 9 ② 8 ③ 17 ④ 6
移动3次可达到目的:
从 ③ 取 4 张牌放到 ④ (9 8 13 10)
从 ③ 取 3 张牌放到 ② (9 11 10 10)
从 ② 取 1 张牌放到 ① (10 10 10 10)。
[输 入]:
N(N 堆纸牌,1 <= N <= 100)
A1 A2 … An (N 堆纸牌,每堆纸牌初始数,l<= Ai <=10000)
[输 出]:
所有堆均达到相等时的最少移动次数。‘
[输入输出样例]
输入:
4
9 8 17 6
输出:
3
算法分析:
在模拟移动的过程中去找到贪心的规律
从左到右移动,当前堆扑克数不为平均数时会产生移动,只需考虑移动一次的情况。>则向右移动,自己剩下平均数,<则右边向自己移动,使自己成为平均数。
if ( a[i]!=ave ) j++, a[i+1]+=a[i]-ave;
时间复杂度为O(n)
3. 国王游戏(2013 年 NOIP提高组) (game.cpp/c/pas)
【问题描述】
恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、 右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。 排好队后,所有的大臣都会获得国王奖赏的若干金币, 每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。
【输入】
输入文件为 game.in。
第一行包含一个整数 n,表示大臣的人数。
第二行包含两个整数 a 和 b,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
【输出】
输出文件名为 game.out。
输出只有一行,包含一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。
【输入输出样例】
game.in | game.out |
31 12 37 44 6 | 2 |
【输入输出样例说明】
按 1、2、3 号大臣这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;
按 1、3、2 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;
按 2、1、3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;
按 2、3、1 这样排列队伍,获得奖赏最多的大臣所获得金币数为 9;
按 3、1、2 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2;
按 3、2、1 这样排列队伍,获得奖赏最多的大臣所获得金币数为 9。
因此,奖赏最多的大臣最少获得 2 个金币,答案输出 2。
【数据范围】
对于 20%的数据,有 1≤ n≤ 10,0 < a、b < 8;
对于 40%的数据,有 1≤ n≤20,0 < a、b < 8;
对于 60%的数据,有 1≤ n≤100;
对于 60%的数据,保证答案不超过 10^9;
对于 100%的数据,有 1 ≤ n ≤1,000,0 < a、b < 10000。
算法分析:
如何排列队伍是本题的难点
使得获得奖赏最多的大臣,所获奖赏尽可能的少。
排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数
一个是要让乘积小一点儿,然后右手上的数要大一点儿
这个可以有数学证明,赛场上不一定有时间,本题可以考虑到排序中的因素并不多,以左右手上的数字乘积来排序还是容易想到的,然后多设计几组数据来测试验证一下
然后累乘的乘积可能会超过long long,所以还要用到高精度乘法,然后高精度除以单精度
4. 【NOIP2015-D2T1】跳石头
Description
一年一度的“跳石头”比赛又要开始了!
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。
Input
输入文件名为stone.in。
输入文件第一行包含三个整数L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。
接下来N行,每行一个整数,第i行的整数Di(0 < Di < L)表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
Output
输出文件名为stone.out。
输出文件只包含一个整数,即最短跳跃距离的最大值。
Sample Input
25 5 2
2
11
14
17
21
Sample Output
4
Hint
【输入输出样例1说明】
将与起点距离为2和14的两个岩石移走后,最短的跳跃距离为4(从与起点距离17的岩石跳到距离21的岩石,或者从距离21的岩石跳到终点)。
【数据规模与约定】
对于20%的数据,0≤M≤N≤10。
对于50%的数据,0≤M≤N≤100。
对于100%的数据,0≤M≤N≤50,000,1≤L≤1,000,000,000。
算法分析:
本题比较容易想到的是将岩石间的距离从小到大排序,然后贪心处理M块石头即可
排序的时间复杂度为nlog(n)
去掉一块石头后,两个距离合并为一个距离要插入进去,可以用堆来处理,时间复杂度也差不多,应该是可以过
本题也可以用二分的方法来处理,网上的正解大多采用的二分的方法(是不是上面的解法有问题?)
将L来不断二分,mid=二分的值(作为最短跳跃距离的最大值)。
如果到当前石头跳跃的距离的值<mid,说明该块石头需要被移走,j++;
否则下一次跳跃的距离就以当前石头为起点
n块石头处理完毕后,如果j>M,则说明mid大了,需要二分左区间,否则二分右区间