《挑战程序设计竞赛》阅读笔记

第一章:准备篇

1.6 轻松热身

ants 问题:把相遇的两个蚂蚁当作交换,当作无障碍
增加难度的抽签问题:把其中一项分离出来,复杂度是n3logn;分离两项,预先把两项相加的结果排序,复杂度使n2logn
重要思想:从复杂度较高的算法出发,不断降低复杂度直到满足问题要求。

2.1 最基础的穷竭搜索

深度优先:比广度优先更容易编写递归函数,适合遍历所有元素。一般与状态数相比,递归深度不会太大,可认为更节省内存。
广度优先:适合寻找最短路径。

2.2 贪心算法

按照每次找最优的思想逐步往下做,直到完成。
找最优的时候,首先要想到结果是不是真的符合题意的最优解,如果贪心算法不能找到该最优解,看看是不是近似的最优解。如果都不是,则不能使用贪心算法。

2.3 记录结果再利用的“动态规划”

(1)背包问题

思路一(P52页下面,P54用穷竭搜索的写法):用dp[i][j]记录从第i个物品开始挑选总重小于j的物品的总价值,因此是逆推。

思路二(P55中间):dp[i+1][j]记录从0到i+1个物品中选出总重量不超过j的物品时总价值的最大值。因此是正推。

思路三(P55页下面):dp数组的含义和思路二的一样,只是状态转移的想法不一样:**“前i个物品中选取总重量不超过j时的状态” ** 向 “前i+1个物品中选取总重不超过j” 和 **“前i+1个物品中选取总重不超过j+w[i]时的状态”**转移。不太好想,掌握前两种思路即可。

void solve()
{
	for(int i = 0; i < n; i++)
		for(int j = 0;j < n; j++)
		{
			dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
			if(j + w[i] <= W)
				dp[i + 1][j + w[i]] = max(dp[i + 1][j + w[i]], dp[i][j] + v[i]);
		}

	printf("%d\n", dp[n][W]);
}

注意:P60下面和P61上面的节省内存的写法。P60下面对应的是P55上面,是对上一轮的i求值,因此j要逆序,保证dp[ j-w[i] ]在这一轮没有被计算,仍是上一轮的值。P61上面对应的是P60中间,是对这一轮的i+1求值,因此j要正序,保证dp[ j-w[i] ]被用到时是这一轮计算过的。

(2)01背包问题之 2(P60)

改变了限制条件,大大增加了W的范围,因此O(nW)的复杂度不行。改变DP的含义,可以大大减小复杂度。

2.4 加工并存储数据的数据结构

(1)优先队列

优先队列的一种高效的实现方式是二叉堆,这已经在C++ STL中实现了,引入头文件queue即可。

(2)二叉搜索树

C++ STL中实现了二叉搜索树,引入头文件set或map。set中的每个元素都是相同数据类型的,而map中每个元素是一个键值对。

(3)并查集

P88的食物链问题,书上给的代码如果直接作为POJ 1182的答案,会超时,因为没有进行路径压缩。
参考这篇文章https://blog.csdn.net/qq_41426887/article/details/97180838

2.5 图

2.5.1 单源最短路径问题

 由一个顶点到另一个顶点的最短路径的距离,和从一个顶点到其他所有顶点的最短路径的距离求解的复杂度是一样的,因此采用相同的算法即可。

2.5.1.1 Bellman-Ford

不适合图中有负圈的情况,但如果没有负圈而只有负边的话,是可以的。还可以用来判断图中有无负圈:如果第n次仍然更新了,则有负圈。
思路:不断地循环,每次更新每条边的终点到固定点的距离,知道无法更新为止。

2.5.1.2 Dijkstra

不适合图中有负边的情况。
思路:每次寻找一个到固定点距离最短的边(不包括已经找过的),然后更新它邻边到固定点的距离。为了降低复杂度,可以使用优先队列实现,P102下面。

2.5.2 任意两点间的最短路径 Floyd-Warshall

可以处理负边,但不能处理负圈。
思路:是否经过某顶点,得到相应的最小距离。将所有的顶点都作为是否经过的尝试一遍,即得解。

2.5.3 路径还原(P104)

可以每次保存顶点的前驱,然后从终点开始不断找前一个前驱结点,查找的复杂度为O(|V|)。
也可以不保存前驱结点,查找的时候用相邻的顶点试探,复杂度为O(|E|)。

2.5.4 最小生成树

最小生成树是否存在等价于图是否连通。

2.5.4.1 Prim

求已经选好的顶点到没有选的顶点间的最小距离,然后把相应的未选好的顶点加到选好的集合中。

2.5.4.2 Kruskal

选最小的边,不构成环即可。

(1)POJ3255

https://blog.csdn.net/qq_41426887/article/details/97306042

(2)POJ3723

相当于求几棵树,每棵树的父节点是子结点省钱的依据,那么这应该是最大权生成森林的问题:可以把权重取反,每次取最小的出来,也可以按原来的权重,但每次取最大的权重出来。
https://blog.csdn.net/qq_39861441/article/details/87128715

(3)POJ3169

书上的写法比较简洁,和下面的链接本质是一样的,只不过第一遍可能看不懂,先把链接看懂再看书上的。https://www.cnblogs.com/tanhehe/archive/2013/02/27/2935726.html

2.6 数学问题的解题窍门

2.6.1 辗转相除法

P116中间

int extgcd(int a, int b, int& x, int& y)
{
	int d = a;
	if(b != 0)
	{
		d = extgcd(b, a%b, y, x);  //y是P116上面推导公式 bx' + (a % b) y' = gcd(a, b) 的x', x是推导公式的y'
		y -= (a/b) * x;
	}
	else
	{
		x = 1;
		y = 0
	}
	return d;   //最大公约数
}

2.6.2 有关素数的基础算法

2.6.2.1 素性测试

素性测试、约数枚举、整数分解的复杂度都是O(n0.5),见P118上面。

2.6.2.2 埃氏筛法

求n以内素数的高效方法。 P119

2.6.2.3 区间筛法

对于区间[a, b),预先筛选出[2, b0.5)内的素数,其在[a, b)的倍数被筛掉即可。P121

2.6.3 快速幂运算:反复平方法

P123和P124的代码中mod是原始的n

2.7 一起来挑战GCJ的题目

先自己想,然后总结解题的过程:怎么想到的,怎么实现的。
(1)2009 Round 1C C(P129)
这道题很多解释的都不太详细,如果陷入死角就很难想清楚,下面是详解。https://blog.csdn.net/qq_41426887/article/details/97497732
(2)Millionaire(P132)
https://blog.csdn.net/qq_41426887/article/details/97538286

3 第三章 中级篇

3.1 二分搜索

3.1.4 最大化平均值

(1)根据数学表达式推导。
(2)如果目前没有好的办法直接求得最优解,可以用一定的迭代逼近最优解。

3.2 常用技巧精选(一)

3.2.1 尺取法

反复地推进区间的开头和末尾,来求取满足条件的最小区间的方法。

3.2.1.1 Subsequence(POJ No.3061)

方法一:从头开始,子序列的起始位置不断向后,并对后面进行二分搜索。属于尺取法。
方法二:更简单的尺取法,即头尾不断变化,比方法一更加简洁。

3.2.2 反转(开关问题)

思想:找到一个基准,避免重复
P156 - P157 :集合的整数表示,用一个二进制数即可表示一个整数集合,并且P157的算法可以枚举{0, 1, 2, … , n - 1}中长度为k的所有子集,依然使用二进制数表示。

3.2.3 弹性碰撞

P158 POJ3684 https://blog.csdn.net/yiqzq/article/details/79863840
一定注意题目描述的距离是个坑,应该改成上面网址的那样。
注:两个球碰撞会交换速度,但由于二者下落距离差2R,球的直径正好也是2R,相当于二者正好交换了,上面的球变成了下面的球,下面的球变成了上面的球,在各自H高度的范围内活动。最后,在考虑彼此间下落高度的不同,加上2Ri。

3.2.4 折半搜索

注:不同于传统的双向搜索
有时候,问题的规模比较大,无法枚举所有元素的组合,但能够枚举一半元素的组合。此时,将问题拆成两半后跟别枚举,再合并它们的结果这一方法可以极大地降低复杂度。

3.3 活用各种数据结构

3.3.1 线段树

RMQ: 范围最值问题
基本功能
在这里插入图片描述

3.3.2 树状数组 Binary Indexed Tree

基本功能
在这里插入图片描述
冒泡排序的交换次数 P178
按照所给的例子,根据前一页的树状数组的图模拟一遍,会发现求sum(a[j])得到的就是书上说的那样。

3.3.3 分桶法和平方分割

知道分桶法的概念,了解平方分割的定义和方法。

基于平方分割的RMQ的基本功能
在这里插入图片描述

注意平方分割和线段树的区别
在这里插入图片描述

3.4 熟练掌握动态规划

对于给定的问题,找到合适的递推公式非常重要,要分析问题的本质,加以变型,以得到适合题解的递推公式。

3.4.1 状态压缩DP

状态压缩DP一般只有current和next两个数组,二者不断的互换,以节省空间。关键是定义清除这两个数组的含义、初始状态和更新方法。
P196铺砖问题

3.4.2 矩阵的幂

(1)求幂,用快速幂运算,参考2.6节。
(2)将问题整理成矩阵运算的形式,或许能让问题更易化简。
在这里插入图片描述
P199和P202的例子很有代表性,也就是说每次可以求多个不同的值,而这多个值都能用同一批变量的组合表示,那么就可以化成矩阵形式。

3.5 借助水流解决问题的网络流

3.5.1 最大流

最大流问题的解法是基于Ford-Fulkerson算法的,可参考下面博客中的文字部分https://blog.csdn.net/u012061345/article/details/41214669

3.5.2 最小割

简单的说就是找几条边,删掉这几条边能够使图分为分别包含s和t的两部分,并且这几条边的权重和是所有能正确分割的边的权重和最小的。

最大流的各种变体 P213-P215 —— 重点

其中,有最小流量限制的情况,要注意从S出去的有经过S->v->t->T的b,也有经过S->s->u->T的b,因此最后的结果要把重复的b减去。

3.5.3 二分图匹配

(1)设置一个源点和一个汇点,将原图转化为边权都为1的有向图,从而按照最大流算法求解。
(2)根据二分图的特点,改进算法——P219下面

3.5.4 一般图匹配

了解

3.5.5 匹配、边覆盖、独立集和顶点覆盖

三个关系,可以利用一个值间接地求另一个值

3.5.6 最小费用流

f是最小费用流 <=> 残余网络中没有负圈
证明https://blog.sengxian.com/algorithms/clearcircle, 比书上的更易于理解。
注意:这里的负圈是以cost 作为边权得到的,反向边的cost和正向边正好符号相反,大小相同。
书上P223 的解法其实和上面的定理没有太大关系,因为每次通过cost求一个最短路径,没有涉及到负圈的问题,只不过是说该算法能行,证明了没有负圈。


P226利用了下面的公式,在dist[ ]基础上又加了最短距离差,使得边权(原本是cost)不为负,因此可以使用Dijkstra算法。
在这里插入图片描述

P227的其他情况也比较重要

3.5.7 应用问题

(1)学会用不同的下表范围表示不同的种类,这样方便构建图。
(2)P236通过建立合适的图,以及最小割等于最大流的定理(任何图都适用),可以快速求解。
(3)P238通过将问题转化为最小费用流,很快求解。本来按照往返路径这种传统思路会比较复杂,但是换种思路就简单多了。
(4)P245 对于多个不同的工厂,如果每个工厂生产多个玩具的话,各个工厂都符合下面的公式。
在这里插入图片描述
因此,要对任意一个玩具,对每个工厂都设置不同倍数的成本,这样就能利用最小费用最大流算法求出最小的时间和。

3.6 与平面和空间打交道的计算几何

3.6.1 计算几何基础

P253 注意处理边界情况和避免分类讨论时产生疏漏
对于几何问题求点灯问题的函数,不妨预先准备好相应的模块,以备调用。
P254 详细介绍了计算误差的处理方法,通常的方法是设立一个精度阈值,通过与这个阈值比较进行取舍

3.6.2 极限情况

P256 将三个方程消去vx和vy即可得到关于t2的方程,系数分别为a, b, c。
P257计算yL, yR, xH, yH后的两个判断条件,第二个判断了斜穿的情况,第一个判断了最高点穿过的情况。
这道题的精髓在于:当可行解可以取连续一段的值时,很多时候考虑边界的极限情况就能解决问题。

3.6.3 平面扫描

P258的题中,平面扫描通过维护一个二叉搜索树,可以使单次搜索的代价由O(n)降低为O(logn)。
平面扫描技术的几何问题中降低算法复杂度的常用手段,分为平移和固定一端进行旋转两种方法。

3.6.4 凸包

P262上面先后构造凸包的下侧和上侧,虽然外积的正负性不能作为判断两向量夹角的依据,但是构造上侧和下侧时如果外积为负,就说明出现了凹性,要按照P261中的图那样处理。
P262下面讲到坐标范围变大的情况,这里说的很模糊,就等做换另一种思路求解最远坐标对之间的距离吧。对踵点对的定义看这里
P262下——P263上介绍的旋转卡壳法,以上面的图为例,(i, j)的变化是 0,6 - 1,6 - 1,0 - 2,0 - 3,0 - 4,0 - 4,1 - 4,2 - 4,3 - 4,4 - 4,5 - 4,6 - 5,6 - 5,0 - 6,0。用外积正负作为判断依据,即便是从变化过程来看也不是很懂,但是最终的结果是对的。见下图
在这里插入图片描述

3.6.5 数值积分

沿着坐标轴对立方体进行切片,得到规则的图形,然后利用Simpson公式,可以方便而精确地求解。
在这里插入图片描述

3.7 一起来挑战GCJ的题目(2)

(1)P267 numbers:通过共轭的一对式子发现规律。记住一些明显的数学结论,对解题很有帮助。

(2)P269 No Cheating:建立无向图,判断为无向图,通过P221-P222的结论求最大匹配来间接求解。

(3)P271 Stock Charts:通过对无向图中路径的观察可以发现,如果构造成P272下面的二分图,匹配的数目等于不是起点的顶点数。

(4)P273 Watering Plants:设置一个最大半径,按照折半查找的方法,对每个半径,①遍历所有的圆对,假设该半径的圆能和某圆对的两个圆相切,然后求出该圆的圆心,找到该圆能覆盖的圆的集合,加入数组中,再对下一个圆对进行处理,然后对数组中的任意两个数据判断是否覆盖的集合是全部的圆。②遍历所有的圆,假设该半径的圆和遍历的一个圆同心,后面的步骤同①。
注意:二分搜索进行比较时,如果考虑误差,有时候反而会使结果产生误差,结果的产生对误差是比较敏感的。本题中,对于相切的两个圆,由于误差的原因,可能会导致判断大圆不能包含与之内切的两个圆,因此要额外加上这两个,用 “ | ij ”

(5)P278 Numbers Sets:由于公共质因数不会超过A-B,因此首先将不超过1000000的质数枚举出来,然后对每个质因数,求其在[A, B]中的倍数来得到并查集。最后再遍历这些数,得到不同根的总数。

(6)P280 Wi-fi Towers:按照书上的例子和代码,应该是和s相连的用协议A,和t相连的用协议B,书上正好描述反。对一个信号塔,升级B的费用(边的权重,也就是容量)是正,则与t相连;费用是负,则变为相反数,并与s相连;如果有i能覆盖到j,则从j到i连接一条容量是INF的边。而且,费用是正的这些边要提前把费用加起来,最后减去最小割。最小割就是让花费的代价最小,因为负边变正了,所以希望用到的这些原本是负边的绝对值之后尽量小,毕竟减去时相当于还是负的;而原本是正边的,最小割用到的也希望最小,毕竟要减去,减去后就相当于仍是协议A。当然,求最小割使用最大流等价的。


4 登峰造极——高级篇

4.1 更加复杂的数学问题

4.1.1 矩阵

(1)线性方程组
列主元高斯消元法——书上只实现了未知数和方程数相同的情况 P287的实现方法中,把正在处理的未知数的系数变为1,并没有把第i行中未知数i的系数变为1,而是把第i行其他系数都除以i的系数,把i的系数默认当做1,以后也会不再用到该系数;从第j个式子中消去第i个未知数时,并没有处理第j个式子的第i个未知数的系数,而是对第j个式子中第i个未知数系数以外的系数进行相应的处理,把第i个未知数当做已经消去了。最终,最后一列的数从上到下依次是不同未知数的值。

(2)期望值和方程组
P290的代码中,构建的A和b两个矩阵分别对应上面高斯消元法的前后两个矩阵。A中元素的含义理解为前一页的E(x, y)。将(A, b)每一行都展开,发现每一行都对应前一页的公式,只不过可以动的位置有几个,到各个位置的期望的系数就变成了几分之一。

4.1.2 模运算的世界

(1)逆元
扩展欧几里得算法可以求出ax + by = 1的x和y,而不是a和b的公约数。extgcd()函数已经实现了该算法,利用该函数可以得到a的逆元x。

(2)费马小定理
欧拉定理是费马小定理的推广。

① 马小定理
在这里插入图片描述
② 欧拉定理
在这里插入图片描述
在这里插入图片描述
③ 欧式筛法
在这里插入图片描述
利用埃氏筛法可以在O(MAX_N)内一次性求出1~n的欧拉函数值表,P292实现

(3)线性同余方程组
按照P292下面的思想,每次根据前面一个方程的解推导下一个方程的解。

(4)中国剩余定理
假设 n = ab(其中a和b互素),,以合数n为模数来考虑和以a和b为模数来考虑是等价的。这是思考算法时的一种思想。

(5)n!(n的阶乘)
对于a mod p,除了P294上面说的(n mod p)! 是在第一层递归中计算之外,还要把p的倍数的系数算上,因此有1, 2, … , n/p,对这一列数,注意其中是p的倍数的数相当于是在后面的递归中算的,因此其相乘之积模p仍然是符合(n/p mod p )!的特征,逐层迭代即可得到a mod p。

(6)Cnk mod p
在这里插入图片描述
在这里插入图片描述

4.1.3 计数

(1)容斥原理
在这里插入图片描述
注意适用条件:集合的个数m比较小,并且集合的并集的元素个数容易计算。

(2)莫比乌斯函数
书上的讲解有一些模糊的地方,但是周期为d的约数的字符串共有26d,这很清楚。同时,莫比乌斯函数也定义好了,因此可以求解。
在这里插入图片描述
按照P299的代码求出g(n)即可。

(3)具有对称性的计数
Polya计数定理:先把所有方案重复计算了相同的次数,然后再把结果除以重复的次数的技术方法。对于染色问题,如果旋转后相同视为一种方案的话,那么都符合此定理。
P301的问题,按照书上的推导,最后的得到的表达式是正确的。注意,旋转0,相当于旋转n,实现的时候按旋转n(因为0不是n的因数)。代码实现的最后一行除以n没有看明白,不是简单地除以n,可能这样的结果也对吧。

P304下面使用莫比乌斯函数也可以求解
注意Polya计数定理的使用范围,这是一种思想。
在这里插入图片描述

4.2 找出游戏的必胜策略

4.2.1 游戏与必胜策略

找到必胜态和必败态的规律,递推到初始状态
最优策略就是如果我有必赢的策略(即使也有会失败的策略),我一定会赢

(1)硬币游戏1
j代表Alice,j - A[i] 代表Bob。

(2)A Funny Game
此类游戏的最优策略:模仿对手的策略,作出对称的状态

(3)Euclid’s Game
注意必胜态和必败态在满足一定条件时,走一步一定会交替的情况。这对于分析递归的情况大有好处。

4.2.2 Nim

(1)Nim
记住结论即可
在这里插入图片描述

(2)Georgia and Bob
也叫 Staircase Nim
若N为奇数,最左边的左边补一个棋子,否则不补,然后两两配对,每一对当作一堆石子,每对棋子之间的间隔当作这堆石子的数量。虽然可能会因为左端棋子左移而导致该堆石子数量增加,但另一方可以移动右端的棋子相同的距离来抵消这种变化,使得石子数保持不减少时也不会增加,这样又回到了另一方面对相同状态,因此等价于Nim。

4.2.3 Grundy数

(1)硬币游戏2
比硬币游戏1增加了多堆的限制,比Nim增加了每次取石子的数量在ai中的限制。
当前状态的Grundy值是指,从当前状态出发,任意走一步,可能会到达多种不同的状态,要求的是这些状态的Grundy值以外的最小非负整数。初始状态的Grundy值是0,表示必败态。P316的代码有些歧义,直接看P317的代码。
注意:和Nim不同,一些情况下Grundy可能会增加,学要注意可能出现的循环以致于不分胜负。本题是没有这种情况的。

(2)Cutting Game
记住这个适用于切割纸张的模型,现在理解的还不够。

4.3 成为图论大师之路

4.3.1 强连通分量分解

把分解后的强连通分量缩成一个顶点,就得到了一个DAG(有向无环图)。
具体做法是两次DFS。第一次DFS,回溯时对顶点进行标号,越接近尾部,标号越小。第二次DFS,先对所有的边进行反向,然后从标号最大的顶点开始进行DFS,则每次DFS所遍历的与已经遍历过的定点集合不重复的顶点集合就构成了一个强连通分量。
实现比较简单,见P322

(1)Popular Cows(POJ No.2186)
按照前面的算法,唯一可能有解的是最后一个强连通分量。

4.3.2 2-SAT

SAT问题:判断是否一组布尔变量的真值指派使整个布尔方程为真的问题,是NP完全的。对于满足一定限制条件的SAT问题,还是能够有效求解的。

如果合取范式的每个子句中的文字个数都不超过两个,那么对应的SAT问题又称为2-SAT问题。

按照书上的方法构建一个有向图,然后通过强连通分量来判断使得公式为真的一组合适的布尔变量值。注意,先按顺序为原变量编号,再给原变量的非编号。

(1)Priest John’s Busiest Day(POJ No.3683)
分四种矛盾情况

4.3.3 LCA

LCA:最近公共祖先,即两个节点的公共祖先中距离最近的那个。祖先包括自身。

(1)基于二分搜索的算法
首先介绍了两个点的LCA算法,复杂度是O(n)。然后介绍了对于多个点,降低复杂度的算法,要处理出2k的表。

(2)基于RMQ的算法
看书

(3)Housewife Wind(POJ No.2763)
代码实现的函数add(), sum() 和rmq_init() 的含义不太清楚。

4.4 常用技巧精选(二)

4.4.1 栈的运用

4.4.2 双端队列

完全不理解的部分:P198 P242 P246

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值