8月清北学堂培训 Day5
今天是杨思祺老师的讲授~
最短路练习题:
POJ 1125 Stockbroker Grapevine
有 N 个股票经济人可以互相传递消息,他们之间存在一些单向的通信路径。现在有一个消息要由某个人开始传递给其他所有 人,问应该由哪一个人来传递,才能在最短时间内让所有人都接收到消息。
题解:
全局最短路,裸的 Floyd 不用说了,时间复杂度 O (n3);
POJ 1502 MPI Maelstrom
给出 N 个处理器之间传递信息所需时间矩阵的下三角,求信息 从第一个处理器传到其它所有处理器所需时间最大值。
题解:
我们要建 n2 条边(邻接矩阵),求单元最短路,一遍 Dijkstra 就好了,时间复杂度 O(n2 log n);
POJ 1511 Invitation Cards
N 个点 M 条边的有向图,询问对于所有 i,从点 1 出发到点 i 并返回点 1 所需时间最小值。
题解:
正向建边,一遍单源最短路求点 1 到每个点的最短路;
那第二问我们总不能每个点都求一遍单源最短路吧 QwQ~
那么我们再反着建边,再来一遍单源最短路求 1 到每个点的最短路就好了 。
POJ 1724 ROADS
有 N 个城市和 M 条单向道路,每条道路有长度和收费两个属性。 求在总费用不超过 K 的前提下从城市 1 到城市 N 的最短路。
题解:
一个比较好像的思路:
我可以将每个点拆成 K+1 个点:
然后跑一遍 Dijkstra,过程中时刻注意花费有没有超过 K 。
神仙做法:
Dijkstra,同时维护当前路径总费用,超过费用上限的状态不再转移。
POJ 1797 Heavy Transportation
给出 N 个点 M 条边的无向图,每条边有最大载重。求从点 1 到点 N 能通过的最大重量。
题解:
1. 我们可以二分答案最大重量 mid,然后我们将所有最大载重小于 mid 的边删掉,然后跑一遍 bfs 判断 1 和 n 是否连通即可;
2. 最大生成树,若加入某条边后 1 和 n 在同一集合里,那么新加入的边的最大载重就是答案;
3. 最短路算法:Dijkstra
d [ u ] 从点 1 到点 u 的最大重量;
那么跑 Dijkstra 的时候我们只要改一下松弛条件就好了:
d [ v ] = min { d [ u ], w < u , v > } ;
POJ 1062 昂贵的聘礼
年轻的探险家来到了一个印第安部落里。在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。探险家拿不出这么多金币,便请求酋长降低要求。酋长说:"嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。"探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。
为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的"优惠"Vi。如果两人地位等级差距超过了M,就不能"间接交易"。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。
题解:
可以这么理解:
我们有了一个东西 u,就能少花点钱,只需要 w 就能买到 v 了;替代关系是边,优惠价格是长度,从某物品出发交换至酋长允诺就是一条路径;
不考虑地位限制,从酋长允诺出发求单源最短路即可知道从任意一点出发所需金币;
枚举地位等级区间,区间长度为 M,不在地位等级区间内的点不可经过,枚举量 O ( N );
BZOJ 3040 最短路 *
N 个点,M 条边的有向图,求点 1 到点 N 的最短路(保证存在)。 1 ≤ N ≤ 1000000, 1 ≤ M ≤ 10000000
By lydrainbowcat
边集分为两部分:
1. 随机生成;2. 输入给出;
题解:
采用高效的堆来优化 Dijkstra 算法:
斐波那契堆 ;
配对堆;
事实上,忽略随机生成的边(这些边占大部分)亦可得到正确的解。
priority_queue STL 实现合并:
启发式合并,两个堆,A 和 B,把较小的堆砍了,放到较大的堆里面;
每个点最多产生 log n 次代价;
总时间复杂度:O(n ( log n )2);
最小生成树算法
Prim
Kruskal
Prim
将所有点分为两个集合,已经和点 1 连通的集合 S、未和点 1 连通的集合 T ;
计算集合 T 中每个点 u 和集合 S 的距离:
选取集合 T 中距离 S 最近的点 u,选中对应的边,加入集合 S;
重复上面的过程,直到所有点都被加入集合 S 朴素写法时间复杂度较劣,可以采用堆优化至 O ( ( N + M ) log N );
正确性的证明:
Prim 是一个基于贪心的算法,可以采用归纳法和反证法证明其正确性。
首先证明第一条选中的边 e1 一定包含在某最优解方案中;
如果最优解中不包含边 e1,则加入 e1 一定会出现环,且环上存在比 e1 大的边,用 e1 替换之答案更优;
假设最优解包含前 k 个选中的边,e1, e2, . . . , ek,则类似地 可证明 ek+1 存在于最优解中;
运用归纳法,Prim 算法得到的 n − 1 条边构成最优解;
Kruskal
将所有边按照边权从小到大排序;
依次考虑每一条边 < ui , vi >,如果这条边和之前选中的边形成环,则不选中此边;反之,选中此边;
当考虑遍所有边后,选中的边一定构成了一棵最小生成树需要并查集的支持,时间复杂度一般认为是 O ( M log M );
正确性的证明:
证明依赖于拟阵的知识。
拟阵:只要具有遗传性和交换性就说明是个拟阵;
遗传性:若 S 是一个独立集,那么 S 的子集 S ′ 是独立集。
遗传性的推论:空集是独立集。
交换性:若 A 和 B 是 S 的两个独立集且 |A| < |B|(集合元素个数),那么存在一 个元素 x 满足 x A 且 x ∈ B,使得 A ∪ {x} 是一个独立集
交换性的推论:一个集合的所有极大独立集大小都相同。
例:线性无关组、无向图生成森林。
拟阵最优化问题:将集合中每个元素赋予一个权值,求权值和最 小 (大) 的极大独立集。
拟阵最优化的贪心算法:
维护当前独立集 G,初始为空。将元素按照权值排序,从小到大 枚举元素 x,若 G ∪ {x} 是一个独立集,那么就将 x 加入独立集 并将 x 的权值累加入答案。最后的结果就是权值和最小的及大 独立集。
证明: 如果最优解不包含最小元素 x1,记该集合为 A,创建新的集合 B = {x1},利用交换性不断将 A 中元素加入 B 直到 |A| = |B|, 则有 B 集合为更优解,矛盾。故 x 一定属于最优解。利用数学归纳法,假设已经证明 x1, x2, . . . , xk−1 属于最优解,如果存在最 优解不包含 xk,还是创建新的集合 B {x1, x2, . . . , xk−1, xk }, 利用交换性将元素加入 B 得到更优解,矛盾。
POJ 1258 Agri-Net
有 N 个村庄,村庄之间形成完全图。现给出邻接矩阵,选择总长度尽可能小的边将 N 个村庄连通。
题解:
裸的最小生成树算法,直接 Kruskal;
POJ 2421 Constructing Roads
有 N 个村庄,有一些道路已经存在,现在希望用最少的总长度 将所有村庄连通。
题解:
把存在的道路的边权设置成 0,然后一遍 Kruskal
POJ 2560 Freckles
给出平面上 N 个点,求将所有点连通的最小距离和。
题解:
n2 建边,跑一遍 Kruskal ;
POJ 1789 Truck History
有 N 个编号,每个编号均为 7 位数。两个编号之间的距离定义为其不同位个数,由一个编号生成另一个编号代价为两个编号的 距离。希望用最小总代价从某一编号开始生成所有编号。
题解:
n2 建边,边权是两个编号之间不同位的个数;
BZOJ 1601 灌水
Farmer John 已经决定把水灌到他的 n (1 ≤ n ≤ 300) 块农田,农田被数字 1 到 n 标记。把一块土地进行灌水有两种方法,从其他农田引水,或者这块土地建造水库。建造一个水库需要花费 wi(1 ≤ wi ≤ 100000), 连接两块土地需要花费 pi,j(1 ≤ pi,j ≤ 100000, pi,j = pj,i , pi,i = 0)。
计算 Farmer John 所需的最少代价。
题解:
建立超级水库点,在某点建立水库视为选择长度为 wi 的边将其和超级水库连通,引水就是选择长度为 pi,j 的边。目标是选择长度和尽可能小的边,使得所有点和超级水库连通。
BZOJ 2753 滑雪与时间胶囊
a 来到雪山滑雪,这里分布着 M 条供滑行的轨道和 N 个轨道之间的交点 (同时也是景点),而且每个景点都有一编号 i 和一高度 Hi。a 能从景点 i 滑到景点 j 当且仅当存在一条 i 和 j 之间的边, 且 i 的高度不小于 j 。a 喜欢用最短的滑行路径去访问尽量多的 景点。如果仅仅访问一条路径上的景点,他会觉得数量太少。于是 a 拿出了他随身携带的时间胶囊。这是一种很神奇的药物,吃下之后可以立即回到上个经过的景点(不用移动也不被认为是 a 滑行的距离)。请注意,这种神奇的药物是可以连续食用的,即能够回到较长时间之前到过的景点(比如上上个经过的景点和上上上个经过的景点)。现在,a 站在 1 号景点望着山下的目标,心潮澎湃。他〸分想知道在不考虑时间胶囊消耗的情况下,以最短滑行距离滑到尽量多的景点的方案 (即满足经过景点数最大的前提下使得滑行总距离最小) 。你能帮他求出最短距离和景点数吗?
题解:
能够到达的景点容易通过 DFS 或 BFS 求出。
最优解应当构成树形,所需要的时间即为所有边长度之和。易联想到用最小生成树 Kruskal 算法解决本问题。
如何设置 Kruskal 时边的优先级?如何保证选出的有向边集使得 每个点从 1 出发可达?
以到达点高度为第一关键字,边长度为第二关键字。到达点高度高的边优先,同样高时边长度短的优先。
BZOJ 2561 最小生成树 *
给定一个边带正权的连通无向图 G < V, E >,其中 N = |V|, M = |E|,N 个点从 1 到 N 依次编号,给定三个正整 数 u, v, L (u ≠ v),假设现在加入一条边权为 L 的边 (u, v),那 么需要删掉最少多少条边,才能够使得这条边既可能出现在最小生成树上,也可能出现在最大生成树上?
前置技能:网络流最小割
题解:
考虑 Kruskal 的过程,如果加入所有权值小于 L 的边后,u 和 v 连通,则新边不可能出现在最小生成树中;
反之,如果 u 和 v 没有连通,则新边存在于至少一棵最小生成树中。
故加入所有权值小于 L 的边,求从 u 到 v 的最小割,删去这些 边以保证新边可能出现在最小生成树中。
同理,加入所有权值大于 l 的边,再求从 u 到 v 的最小割,删 去这些边使新边可能出现在最大生成树中。 答案即为两次最小割删边数量之和。
树上倍增
有根树(随意定根)
最近公共祖先
链上信息(和、最值)
优秀的时间复杂度
序列倍增
回忆普通的序列倍增思想,以 ST 表为例。
Fi,j 记录区间 [ i, i + 2j − 1 ] 内信息(区间和或区间最值)
Fi,j = merge (Fi,j−1, Fi+2 j−1,j−1 ) # 取出区间 [l, r] 答案时,使用若干个 Fi,j 即可 # 如果求区间最值,那么取 Fl,k 和 Fr−2 k ,k 即可,其中 k ⌊lo g2(r − l + 1⌋ # 如果求区间和,可以取 Fl,k1 , Fl+2 k1 ,k2 , Fl+2 k1+2 k2 ,k3 , . . . 即可
树上倍增
树上从每个点出发到根结点的链也具有类似的形式。
Fi,j 表示点 i 向上走 2j 步的结点编号;
Fi,0 就是点 i 的父亲节点;
如何求解 u 向上移动 k 步是哪个点?
将 k 写作 2 的幂次之和,如 11 = 23 + 21 + 20。 用 Gi,j 表示 i 向上移动 j 步的结果。
在 O( log N ) 步内完成。
最近公共祖先(LCA)
树上倍增最常见的用处是求解两个点的最近公共祖先。
求解 a 和 b 的最近公共祖先 :
将 a 和 b 调整到相同高度;
判断 a 和 b 是否重合,若重合则该点即为答案;
令 a 和 b 一起向上移动尽可能大的距离,保证移动后两点不重合;
此时两点的父亲结点即为答案,单次询问时间复杂度 O( log N );
最近公共祖先的常见求解方法有:
1. 树上倍增;
2. 树链剖分;
3. DFS 序 + RMQ;
构造 DFS 序:
我们在 dfs 搜索的时候,假如我们搜到了第 u 个点,我们将 u 放进 dfs 序里面,搜索它的子树然后回溯到点 u 的时候我们再将 u 放进 dfs 序里面,这样算过来一个点 u 在 dfs 序中出现的次数为:(u 的儿子个数 + 1);
考虑这样一件事情:
我们在 dfs 序中看到了这么一段:
aaaaaysqnpbbbbb
我们发现了一段连续的 a 和 b ;
那么在最后一个a ~ 第一个b 这段区间内,一定有一个是 a 和 b 的 LCA !
根据 dfs 的顺序可知:递归右子树之前一定会先经过 LCA,那么 LCA 会被扔进 dfs 里面!
所以我们在这一段区间内求出深度最小的点,那个点就是 a 和 b 的 LCA!
向上路径
如何求解从 u 到 v 路径上边权最值?保证 v 是 u 的祖先。
在树上倍增向上走时取移动区间最值更新答案即可。
树上路径
树上路径 记 g = LCA(u, v),则树上从 u 到 v 的路径可以拆分为两段:
从 u 到 g 的路径;
从 g 到 v 的路径 。
如何求解从 u 到 v 路径上的边权和?
将路径拆分为两段向上路径,分别求解其答案再合并。
树链剖分
树链:不拐弯的路径;
剖分:每个点属于一条链;
应当具有优良的性质。
重儿子:子树大小最大的子结点;
重链:从一点出发,一直选择重儿子向下走,走到叶子;
轻边:不属于任何一条重链的边;
每一条边要么属于重链要么属于轻边;
从任何一个点出发往根结点走,路径一定是轻边重链交替的,数量嘛,只不过是谁比谁多 1 的问题。
分析:从任意一点 u 走到根结点,经过的重链、轻边个数的量级是多少?
走一条重链、轻边,子树大小至少翻倍,故易知 O ( log N ) 。
树链剖分有两个 dfs:
第一次 dfs:统计每个子树的大小;
第二次 dfs:走每个点的重链,再走轻边;
重链是在 dfs 序中是连续的一段。
一些记号:
depu 表示点 u 的深度;
fatu 表示点 u 的父亲节点;
topu 表示点 u 所在重链的顶端结点;
dfnu 表示重儿子优先 DFS 下点 u 的时间戳;
endu 表示重儿子优先 DFS 下点 u 子树最大时间戳;
求点 a 和点 b 的最近公共祖先。
记 ta = topa , tb = topb
如果 ta = tb,则 a 和 b 在同一条重链,深度较小的为 LCA ;
如果 depta > deptb,那么令 a = fatta ;
如果 depta < deptb,那么令 b = fattb ;
SPOJ QTREE Query on a tree
You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3 . . . N − 1. We will ask you to perfrom some instructions of the following form:
CHANGE i ti : change the cost of the i-th edge to ti;
QUERY a b : ask for the maximum edge cost on the path from node a to node b;
题解:
我们先将这棵树的 dfs 序求出来,那么问题就是:“ 单点修改,区间求最大值 ” 的问题,可以用线段树优化;
树链剖分 + 线段树维护区间最大值 。
BZOJ 1036 树的统计
一棵树上有 n 个节点,编号分别为 1 到 n,每个节点都有一个权 值 w。 我们将以下面的形式来要求你对这棵树完成一些操作:
1. CHANGE u t : 把结点 u 的权值改为 t;
2. QMAX u v: 询问从点 u 到点 v 的路径上的节点的最大权值;
3. QSUM u v: 询问从点 u 到点 v 的路径上的节点的权值和;
注意:从点 u 到点 v 的路径上的节点包括 u 和 v 本身。
题解:
类似于上一题,也可以转化成:“ 单点修改,区间求最大值,区间求和 ”之类的题目,可以与线段树结合;
树链剖分 + 线段树维护区间最大值、区间和 ;
BZOJ 4034
有一棵点数为 N 的树,以点 1 为根,且树点有边权。然后有 M 个操作,分为三种:
1. 把某个节点 x 的点权增加 a 。
2. 把某个节点 x 为根的子树中所有点的点权都增加 a 。
3. 询问某个节点 x 到根的路径中所有点的点权和。
题解:
第一种操作是单点加法;
子树在 dfs 序里面是连续的一段,其实就是区间加法啦;
第三种操作是区间询问;
树链剖分 + 线段树单点加、区间加、区间求和;
BZOJ 2243 染色
给定一棵有 n 个节点的无根树和 m 个操作,操作有 2 类:
将节点 a 到节点 b 路径上所有点都染成颜色 c;
询问节点 a 到节点 b 路径上的颜色段数量(连续相同颜色被认为是同一段),例:“112221”由 3 段组成:“11”、“222”和“1”。
请你写一个程序依次完成这 m 个操作。
题解:
树链剖分 + 线段树 ;
维护子区间内颜色段树数;
合并时判断左子区间最右点和右子区间最左点颜色是否相同,不相同的话直接将左右区间的颜色段数加起来就好了,相同的话再减一;
树链剖分查询答案时还要判断上一条链和本条链相邻结点颜色是否相同(轻边的两端);
BZOJ 2238 MST
给出一个 N 个点 M 条边的无向带权图,以及 Q 个询问,每次询问在图中删掉一条边后图的最小生成树。( 各询问间独立,每次询问不对之后的询问产生影响,即被删掉的边在下一条询问中依然存在 );
题解:
分析
首先求出最小生成树;
如果删去的不是最小生成树中的边,则答案不变;
如果删去的最小生成树中的边,那么会将原先的最小生成树分成两部分,所以我们需要再找一些边使得这两部分再次联通;
一条非树边可以代替其两点之间路径上的任何一条边;
实现
求出最小生成树;
对最小生成树进行树链剖分;
用线段树维护区间最小值,初始全部为 ∞ ;
对于非树边 < u , v , w >,用 w 更新 u 到 v 路;
强连通分量
强连通分量
在有向图中,如果两个点之间存在两个方向的路径,则称两个点强连通;如果有向图的任何两个顶点都强连通,则称其为强连通图;
有向图的极大强连通子图即为强连通分量。
缩点
强连通分量最常见的用途是将能互相到达的点集缩为一个新的点,建立的新图一定是有向无环图(那么我们就可以拓扑排序了)。
Tarjan 算法
Tarjan 算法可以在 O ( N + M ) 的时间复杂度内求解有向图的所有强连通分量。
首先提取出有向图的一棵搜索树作为基础框架。
dfnu 为 u 点的 DFS 时间戳;
lowu 为 u 点出发能追溯到的最小时间戳;
lowu = min { dfnu , lowv1 , dfnv2 } ,其中 < u, v1 > 为树枝边,< u, v2 > 为后向边。
BZOJ 2208 连通数
度量一个有向图的连通情况的一个指标是连通数,指图中可达的顶点对数量。 例如下图中连通数为 14。 现希望求出给定图的连通数。
N ≤ 2000
题解:
1. Floyd 传递闭包;
2. 多次 BFS 暴力,bitset 记录;
3. Tarjan 缩点,设强连通分量的大小是 k,那么这个强连通分量对答案的贡献是 k2 ,DAG 上 BFS,bitset 记录;
bitset 压位:
bitset 其实是个类似于 bool 的东西,但是比 bool 要快;
我们开一个 bitset < 2000 > i ,里面存的是一个 2000 位的二进制数(只有 0 / 1),我们可以用来压位(类似于状压);
设第 j 位为 1 表示点 i 能够走到点 j;
那么对于点 i,它贡献的答案就是 i . count ( 里面 1 的个数,也就是点 i 能到达的点的数量 ) + 1 ( 本身 );
我们在输入的时候就可以搞这个是吧:
若有一条边 < u , v >,我们让 u 的第 v 位变成 1 就好了:u | = 1 << v;
POJ 2186 Popular Cows
每头牛都有一个梦想:成为一个群体中最受欢迎的名牛!在一个 有 N(1 ≤ N ≤ 10000) 头牛的牛群中,给你 M(1 ≤ M ≤ 50000) 个二元组 ( A , B ), 表示 A 认为 B 是受欢迎的。既然受欢迎是可传递的,那么如果 A 认为 B 受欢迎,B 又认为 C 受欢迎, 则 A 也 会认为 C 是受欢迎的,哪怕这不是〸分明确的规定。你的任务是计算被所有其它的牛都喜欢的牛的个数。
题解:
Tarjan 缩点,变为 DAG ;
DAG 中如果只有一个点出度为 0,则该点代表的 SCC 即为答案;
DAG 中如果有多于一个点出度为 0,则不存在答案 。
POJ 1236 Network of Schools
给定 N 所学校和网络,每个学校收到软件后会分发给相邻的学校(单向边)。 求:
1. 若选取一些学校分发软件使得所有学校都可收到,所需最少分发学校数;
2. 若要使任选一所学校分发软件,所有学校都可收到,最少新增边数;
题解:
考虑到强连通分量里的点都能互相到达。
Tarjan 缩点,将强连通分量缩成一个个大点,这样图就变为 DAG ;
第一问,需要给所有入度为 0 的点分发软件,答案就是入度为 0 的点的个数 。
第二问,有一个毒瘤的地方:它要保证选每个学校都能满足条件;
也就是说,我们 Tarjan 缩点之后要加最少的边,使所有点构成一个环。
我们可以从出度为 0 的点向入度为 0 的点连一条边,这样一下能解决两个点使之丢进环里;如果我们发现入度为 0 的点已经丢光光了出度为 0 的点还有,那我们也要把它们丢进去啊(雾,不过这时候我们就可以选之前出度为 0 的点了(一个点可以由多给点转移过来),这样算过来的话,答案就是:max (入度为 0 的点,出度为 0 的点);