描述
开 本: 16开纸 张: 胶版纸包 装: 平装-胶订是否套装: 否国际标准书号ISBN: 9787302571742丛书名: 算法艺术与信息学竞赛
《算法竞赛入门经典——训练指南(升级版)》是算法届大神刘汝佳所著信息学奥赛红宝书——《算法竞赛入门经典》的拓展训练用书。
训练指南2021新版增补了大量ACM/ICPC/NOI/NOIP的新知识点和新题型,优化了部分算法模板,扩增了分类专项练习题。
这是一本单书销售9万册,丛书销售35万册,连UVa线上评测系统创始人、ACM/ICPC国际指导委员Miguel A.Revilla都推荐的算法竞赛训练题集。
这是一本在程序员中家喻户晓、被大量学校广泛采作教材的算法竞赛经典之作。
这不是一本入门图书。想看懂它,需要你具备一定的算法基础。
这本书,如果你能独立完成大部分,你的算法能力完全能达到现今IT公司内程序员的中上水准。
这本书和《算法竞赛入门经典(第2版)》珠联璧合,相辅相成。它会像朋友和知己一样,同你一起探讨和研究问题,直至你打开算法之美的大门!
ACM入门经典,我们相见恨晚!!
《算法竞赛入门经典——训练指南(升级版)》是《算法竞赛入门经典(第2版)》一书的重要补充,旨在补充原书中没有涉及或者讲解得不够详细的内容,从而构建一个更完整的知识体系。本书通过大量有针对性的题目,让抽象复杂的算法和数学具体化、实用化。 《算法竞赛入门经典——训练指南(升级版)》共包括6章,分别为算法设计基础、数学基础、实用数据结构、几何问题、图论算法与模型以及更多算法专题。全书通过206道例题深入浅出地介绍了上述领域的各个知识点、经典思维方式以及程序实现的常见方法和技巧,并在章末给出了丰富的分类习题,供读者查漏补缺和强化学习效果。 《算法竞赛入门经典——训练指南(升级版)》题目多选自近年来ACM/ICPC区域赛和总决赛真题,内容全面,信息量大,覆盖了常见算法竞赛中的大多数细分知识点。书中还给出了所有重要的经典算法的完整程序,以及重要例题的核心代码,既适合选手自学,也方便院校和培训机构组织学生学习和训练。
第1章 算法设计基础 1
1.1 思维的体操 1
1.2 问题求解常见策略 14
1.3 高效算法设计举例 36
1.4 动态规划专题 55
1.5 小结与习题 71
1.5.1 问题求解策略 72
1.5.2 高效算法设计 80
1.5.3 动态规划 83
第2章 数学基础 86
2.1 基本计数方法 86
2.2 递推关系 92
2.3 数论 101
2.3.1 基本概念 102
2.3.2 模方程 107
2.3.3 线性筛 113
2.3.4 积性函数与莫比乌斯反演 116
2.3.5 筛法求解积性函数 118
2.4 组合游戏 124
2.5 概率与数学期望 130
2.6 置换及其应用 135
2.7 矩阵和线性方程组 142
2.8 快速傅里叶变换(FFT) 154
2.9 数值方法 165
2.10 小结与习题 171
2.10.1 组合计数 173
2.10.2 数论 177
2.10.3 组合游戏 181
2.10.4 概率 183
2.10.5 置换 184
2.10.6 矩阵与线性方程组 186
2.10.7 快速傅里叶变换(FFT) 188
2.10.8 数值方法 189
第3章 实用数据结构 192
3.1 基础数据结构回顾 192
3.1.1 抽象数据类型(ADT) 192
3.1.2 优先队列 194
3.1.3 并查集 197
3.2 区间信息的维护与查询 199
3.2.1 二叉索引树(树状数组) 200
3.2.2 RMQ问题 202
3.2.3 线段树(1):点修改 204
3.2.4 线段树(2):区间修改 207
3.3 字符串(1) 219
3.3.1 Trie 219
3.3.2 KMP算法 222
3.3.3 Aho-Corasick自动机 225
3.4 字符串(2) 229
3.4.1 后缀数组 229
3.4.2 长公共前缀(LCP) 233
3.4.3 基于哈希值的LCP算法 235
3.4.4 回文的Manacher算法 238
3.5 字符串(3) 240
3.5.1 后缀自动机的性质 241
3.5.2 后缀链接树(Suffix Link Tree) 241
3.5.3 后缀自动机的构造算法 242
3.6 排序二叉树 255
3.6.1 基本概念 255
3.6.2 用Treap实现名次树 258
3.6.3 用伸展树实现可分裂与合并的序列 266
3.7 树的经典问题与方法 270
3.8 动态树与LCT 289
3.9 离线算法 299
3.10 kd-Tree 312
3.11 可持久化数据结构 319
3.12 小结与习题 331
3.12.1 基础数据结构 332
3.12.2 区间信息维护 333
3.12.3 字符串算法 335
3.12.4 排序二叉树 338
3.12.5 树的经典问题与方法 339
3.12.6 动态树与LCT 342
3.12.7 离线算法 344
3.12.8 kd-Tree 347
3.12.9 可持久化数据结构 348
第4章 几何问题 351
4.1 二维几何基础 351
4.1.1 基本运算 352
4.1.2 点和直线 353
4.1.3 多边形 355
4.1.4 例题选讲 356
4.1.5 二维几何小结 359
4.2 与圆和球有关的计算问题 360
4.2.1 圆的相关计算 360
4.2.2 球面相关问题 366
4.3 二维几何常用算法 366
4.3.1 点在多边形内的判定 366
4.3.2 凸包 368
4.3.3 半平面交 372
4.3.4 平面区域 378
4.4 三维几何基础 382
4.4.1 三维点积 383
4.4.2 三维叉积 384
4.4.3 三维凸包 386
4.4.4 例题选讲 388
4.4.5 三维几何小结 392
4.5 小结与习题 393
4.5.1 基础题目 393
4.5.2 二维几何计算 395
4.5.3 几何算法 398
4.5.4 三维几何 403
第5章 图论算法与模型 408
5.1 基础题目选讲 408
5.2 深度优先遍历 411
5.2.1 无向图的割顶和桥 413
5.2.2 无向图的双连通分量 416
5.2.3 有向图的强连通分量 420
5.2.4 2-SAT问题 424
5.3 短路问题 428
5.3.1 再谈Dijkstra算法 428
5.3.2 再谈Bellman-Ford算法 432
5.3.3 例题选讲 436
5.4 生成树相关问题 443
5.5 二分图匹配 447
5.5.1 二分图匹配 447
5.5.2 二分图完美匹配 448
5.5.3 稳定婚姻问题 452
5.5.4 常见模型 455
5.6 网络流问题 457
5.6.1 短增广路算法 457
5.6.2 小费用流算法 462
5.6.3 建模与模型变换 464
5.6.4 例题选讲 467
5.7 小结与习题 472
5.7.1 基础知识和算法 472
5.7.2 DFS及其应用 472
5.7.3 短路及其应用 476
5.7.4 小生成树 477
5.7.5 二分图匹配 479
5.7.6 网络流 480
第6章 更多算法专题 484
6.1 轮廓线动态规划 484
6.2 嵌套和分块数据结构 490
6.3 暴力法专题 500
6.3.1 路径寻找问题 500
6.3.2 对抗搜索 505
6.3.3 精确覆盖问题和DLX算法 510
6.4 几何专题 516
6.4.1 仿射变换与矩阵 516
6.4.2 离散化和扫描法 518
6.4.3 运动规划 527
6.5 数学专题 529
6.5.1 小专题集锦 530
6.5.2 线性规划 532
6.6 浅谈代码设计与静态查错 533
6.6.1 简单的Bash 533
6.6.2 《仙剑奇侠传四》之后的战役 542
6.7 小结与习题 548
6.7.1 轮廓线上的动态规划 548
6.7.2 数据结构综合应用 550
6.7.3 暴力法 557
6.7.4 几何专题 562
6.7.5 数学专题 567
6.7.6 代码组织与调试 569
附录 Java、C#和Python语言简介 575
主要参考书目 582
许多计算机相关专业的人,毕业之后除了为应付面试外,基本都很少再去碰算法。而在实际的产品或者项目开发过程中,大多数人也没有必要亲自去实现复杂的算法。因此,算法渐渐淡出程序员的日常生活。同时,在现实生活中有另外一种声音:程序员的生活太纠结,coding的速度永远跟不上需求变化的速度,提需求的客户似乎成了程序员的“天敌”,成了他们“苦逼”生活的罪魁祸首。
那么,一本讲算法比赛的书跟这又有多少关系呢?就从我自身的经历说起吧。我不是计算机科班出身,但因种种原因进入了这个行业,而且是从一个很低的起点进入的。于是我像所有人一样,平时很难静下心来学习算法,有了面试就去临时抱本书突击一下。终于有一天我受不了了这种循环,我扪心自问:难道只有为了某个急功近利的目的我才愿意去付出自己的时间吗?佛家有句话叫“凡夫求果,菩萨求因”,我就想,既然成不了圣人,就学一回圣人吧。
因缘际会,我接触到了“入门经典”及其作者刘汝佳,于是一发不可收,写这本书的过程也变成了修行与学习的过程。慢慢地,我发现算法对于实际工作的人而言,有着比应付面试更大的价值。所谓的算法、组件、模式,就像是一些基础的原材料,对于优秀的建筑师来说,需要透彻地理解(不一定写得很熟练)它们的关键性。因为一个错误的设计,对于系统来说,所要付出的代价远比一般的程序bug要高得多。更进一步说,现在做软件的为什么苦,为什么抱怨需求变化快?因为解决问题的思维方式出现了偏差。需求分析不是简单地拿着需求,直接翻译成代码—这是层次的实现。算法分析的意义,更多地不在于性能,不在于那些脑筋急转弯,而在于发现纷繁复杂的问题背后的“不变式”,而这正是本书要着力与大家分享的地方。
2012年,《算法竞赛入门经典——训练指南》版出版面市。时光荏苒,转眼间已过去8年时间。这些年间,竞赛界出现了很多新的知识点和题目类型;另外,在人工智能大潮的感召下,更多的学子参与到了算法竞赛中。为了能够为这些新增的知识点提供一些例题讲解以及习题练习,大概从两年前开始,笔者和刘汝佳老师开始考虑对本书进行增补。
我们对近些年来ACM/ICPC等信息学竞赛中新增的知识和点题型进行了仔细的斟酌和比对,通过多次筛选,挑选出了一些极具代表性的习题,增补到了原书中。这些习题基本都是ACM/ICPC各个区域比赛以及世界总决 赛的真题以及各国信息学竞赛中的真题,部分章节中几乎有一半习题被更新为题目。
具体来说,《算法竞赛入门经典——训练指南》升级版新增的内容主要有:
第2章,补充了数学部分线性筛、莫比乌斯反演以及积性函数。将FFT放到第2章并且进行了扩写,并且增加了NTT以及FWT相关例题。
第3章,补充了字符串部分,主要是后缀自动机、Manacher字符串相关算法;倍增LCA、点分治、树链剖分等树上经典问题与方法;LCT的相关例题以及习题;离线算法,包括基于时间分治、整体二分、莫队;kd-Tree;可持久化数据结构,包括权值线段树、Trie、树状数组、Treap的可持久化版本。
第4章,补充了一道平面切割立方体的3D几何例题(LA 5808)。
第5章,完善了一些例题题解的描述。
第6章,补充了树分块和树上莫队等内容。
以上新增部分的内容提纲及题解审阅由刘汝佳老师完成,具体的题解及代码编写由陈锋完成。另外,为了提升读者的做题体验感,本书在牛客竞赛网上提供了各章节绝大多数题目的题单(扫描封底“文泉云盘”二维码,获取题单及其他资源下载方式),读者可以获得更好的提交速度以及体验。当然,读者也可以挑选自己薄弱的章节,有针对性地进行题目的练习。
第 1章 算法设计基础 算法设计基础
在《算法竞赛入门经典(第2版)》一书中,已经讨论过算法分析、渐进时间复杂度等基本概念,以及分治、贪心、动态规划等常见的算法设计方法。本章是《算法竞赛入门经典(第2版)》的延伸,通过例题介绍更多的算法设计方法和技巧。
1.1 思维的体操 例题1 勇者斗恶龙(The Dragon of Loowater, UVa 11292)
假设你是一位国王,你的王国里有一条n个头的恶龙,你希望雇一些骑士把它杀死(即砍掉恶龙的所有头)。王国里有m个骑士可以雇用,一个能力值为x的骑士可以砍掉恶龙一个直径不超过x的头,且需要支付x个金币。如何雇用骑士才能砍掉恶龙的所有头,且需要支付的金币少?注意,一个骑士只能砍一个头(且不能被雇用两次)。
【输入格式】
输入包含多组数据。每组数据:行为正整数n和m(1≤n,m≤20 000);接下来n行每行为一个正整数,即恶龙每个头的直径;再接下来m行每行为一个正整数,即每个骑士的能力。输入结束标志为n=m=0。
【输出格式】
对于每组数据,输出少花费。如果无解,输出“Loowater is doomed!”。
【样例输入】
2 3
5
4
7
8
4
2 1
5
5
10
0
【样例输出】
11
Loowater is doomed!
【分析】
能力强的骑士开价高是合理的,但如果被你派去砍恶龙的一个很弱的头,就是浪费人
算法竞赛入门经典—训练指南
·2·
才了。因此,可以把雇用来的骑士按照能力从小到大排序,把恶龙的所有头按照直径从小到大排序,一个一个砍就可以了。当然,不能砍掉“当前需要砍的头”的骑士就不要雇用了。代码如下。
#include
#include // 因为用到了 sort
using namespace std;
const int maxn = 20000 5;
int A[maxn], B[maxn];
int main() {
int n, m;
while(scanf(“%d%d”, &n, m) == 2 && n { while(scanf(“%d%d”, &n, m) == 2 && n {
for(int i = 0; < n; i ) scanf(“%d”, &A[i]); for(int i = 0; < n; i ) scanf(“%d”, &A[i]);
for(int i = 0; < m; i ) scanf(“%d”, &B[i]); for(int i = 0; < m; i ) scanf(“%d”, &B[i]);
sort(A, A n);
sort(B, B m);
int cur = 0; int cur = 0; // 当前需要砍掉的头编号
int cost = 0; int cost = 0; // 当前总费用
for(int i = 0; < m; i ) for(int i = 0; < m; i )
if(B[i] >= A[cur]) { if(B[i] >= A[cur]) {
cost = B[i]; cost = B[i]; // 雇用该骑士
if( cur == n) break; if( cur == n) break; // 如果头已经砍完,及时退出循环
}
if(cur < n) printf(“Loowater is doomed! if(cur < n) printf(“Loowater is doomed! n”);
else p rintf(“%d n”, cost); n”, cost);
}
return 0;
}
例题2 突击战(Commando War, UVa 11729)
假设你带领团队去执行一组突击任务,你有n个部下,每个部下需要完成一项任务。第i个部下需要你花Bi分钟交代任务,然后他会立刻独立地、无间断地执行Ji分钟后完成任务。你需要选择交代任务的顺序,使得所有任务尽早执行完毕(即后一个执行完的任务应尽早结束)。注意,不能同时给两个部下交代任务,但部下们可以同时执行他们各自的任务。
【输入格式】
输入包含多组数据。每组数据:行为部下的个数n(1≤n≤1000);接下来n行每行为两个正整数B和J(1≤B≤10 000,1≤J≤10 000),即交代任务的时间和执行任务的时间。输入结束标志为n=0。
【输出格式】
对于每组数据,输出所有任务完成的短时间。
【样例输入】
3
2 5
第 1 章 算法设计基础
·3·
3 2
2 1
3
3
4
5
0
【样例输出】
Case 1: 8
Case 2: 15
【分析】
直觉告诉我们,执行时间较长的任务应该先交代。于是我们想到这样一个贪心算法:按照J从大到小的顺序给各个任务排序,然后依次交代。代码如下。
#include
#include
#include
using namespace std;
struct Job {
int j, b;
bool operator < (const Job& x) { bool operator < (const Job& x) { // 运算符重载,不要忘记 const 修饰符
return j > x.j;
}
};
int main() {
int n, b, j, kase = 1; int n, b, j, kase = 1;
while(scanf(“%d”, &n) == 1 && { &n) == 1 && {
vector v;
for(int i = 0; < n; i ) { for(int i = 0; < n; i ) {
scanf(“%d%d”, &b, j); v.push_back((Job){j,b}); scanf(“%d%d”, &b, j); v.push_back((Job){j,b});
}
sort(v.begin(), end()); sort(v.begin(), end()); // 使用 Job 类自己的 < 运算符排序 运算符排序
int s = 0;
int ans = 0;
for(int i = 0; < n; i ) { for(int i = 0; < n; i ) {
s = v[i].b; = v[i].b; // 当前任务的开始执行时间
ans = max(ans, s v[i].j); ans = max(ans, s v[i].j); // 更新任务执行完毕时的晚间
}
printf(“Case %d: d n”, kase , ans);
}
return 0;
}
上述代码直接交上去就可以通过测试了。
可是为什么这样做是对的呢?假设我们交换两个相邻的任务X和Y(交换前X在Y之
算法竞赛入门经典—训练指南
·4·
前,交换后Y 在X 之前),不难发现这对其他任务的完成时间没有影响,那么这两个任务呢?
? 情况一:交换之前,任务Y 比X 先结束,如图1-1(a)所示。不难发现,交换之后X
的结束时间延后,Y 的结束时间提前,终结果不会变好。
? 情况二:交换之前,X 比Y 先结束,则交换后结果变好的充要条件是:交换后X 的结
束时间比交换前Y 的结束时间早(交换后Y 的结束时间肯定变早了)。反之,如果出现
如图1-1(b)所示的情况,则交换后的结果不会变好,交换后结果变好的充要条件可
以写成B[Y] B[X] J[X]算法的依据。
时间
交代执行
交代执行
交代执行
交待执行
时间
交代执行
交代执行
交代执行
交代执行
先后顺序是关键
B[Y] B[X] J[X]
B[X] B[Y] J[Y]
X
Y
Y
X
X
Y
Y
X
(a) (b)
图 1-1
评论
还没有评论。