[做题笔记 #3] 图论
- [做题笔记 #3] 图论
[做题笔记 #3] 图论
[ ] 里的是不确定的。
1~11 是 2024 年国庆的题。
1. P6175 无向图的最小环问题
Floyd 求最小环板子。大概是在 Floyd 的过程中,在算当前中继点对最短路的贡献之前,处理它和编号更小的点形成的环(利用当前的最短路信息),这样最短路就不会包含枚举到的点形成的另外两条边,于是算环时不会出问题。
具体见代码。
2024.11.15
2. P4568 [JLOI2011] 飞行路线
分层图最短路。感觉可能很经典,但我之前没怎么做过分层图的题。
总结:特殊走几条边就把图分几 \(+ 1\) 层(可以往下走几层),层内的图就是原图的复制品,而层和层之间的边用于实现特殊移动。
2024.11.15
3. P5304 [GXOI/GZOI2019] 旅行者
二进制划分 + 最短路 做法
- 每次看一个二进制位,把编号的这一位为 \(0\) 的作为起点,这一位为 \(1\) 的最为终点。建立超级源点连向所有起点,建立超级汇点由所有终点连向它。
- 因为两个不同的非负整数都会有至少一个二进制位不同,所以任意一对点一定会在某一轮中一个是起点,另一个是终点。
- 因为取 \(\min\),求 \(\min\) 的过程中可以重复,因此不用担心此做法带来的重复的问题。
- 感觉这个做法很有启发性。它可以用来解决的问题有三个特征:
- 要求出一批东西两两之间的贡献合到一起的结果。
- 合到一起的过程中允许重复。
- 每轮贡献的计算的时间复杂度 不受 要算贡献的点的个数 限制,而 [可能] 更大。
- 这种做法的本质:
- 考虑 CDQ 分治。CDQ 分治的划分类似于按二进制位划分,算左对右的贡献,但是它可以解决不允许重复的问题。但是这样的代价是每次要分治下去,即下面的每一层都是被割成一段段的,这些段互相之间不算贡献。
- 但是如果每段(每次)算贡献的代价不被段的长度所限制,而可能更大(比如说和总点数有关),CDQ 的复杂度就会很大。
- 这种做法其实是抓住了可重的特性,把 CDQ 分治中每一层的贡献一起算。这样就把时间复杂度削减下来了。
- 这种做法的本质也说明了它的优势、它为什么不能被 CDQ 分治或二进制分组替代。
- 我之前尝试过用此做法来算贡献的异或和,我试图通过规定位数 + 按位取反来使每对点间的贡献都算奇数次(偶数次的取反后就算奇数次,合到一起还是奇数),但哪些取反哪些不取又要划分,我还没想清楚。
正反建图 + 最短路 做法
正确性有点神秘。
对于每条边去求。注意判掉有端点不可达的情况、起点终点相同的情况(记 frm)。
关于正确性:水哥给我讲过,记不太清了,好像是答案的那条路径一定能找到一个分割的地方,还是说所有路径都可以?
2024.11.15
4. P6961 [NEERC2017] Journey from Petersburg to Moscow
感觉是神秘且很牛的题。
前 \(k\) 大,考虑枚举一条边作为第 \(k\) 大,复制一张原图作为新图,在新图上,把边权 \(\geq\) 它的边都减去它,把边权 \(<\) 它的边的边权都设为 \(0\),跑最短路,加上 \(k \times\) 它的边权 作为当前的值。分类讨论这条边与路径上第 \(k\) 大的边的大小关系,发现一定大于等于真正的答案。枚举到某条边,它就是第 \(k\) 大的边时,用它算出来的值就等于答案。但我们并不知道这条边是哪一条,于是求每一次的值的 \(\min\) 作为答案。但是还有一种情况,就是边不足 \(k\) 条时应该直接跑最短路。容易发现即使边 \(\geq k\) 条时,直接跑最短路也不会比答案更短,于是再和直接跑最短路的结果(原图的最短路)取 \(\min\) 即可。
具体见(%%%):[P6961 NEERC2017]Journey from Petersburg to Moscow - 洛谷专栏 (luogu.com.cn)。
2024.11.15
5. P4899 [IOI2018] werewolf 狼人
用两棵 Kruskal 重构树维护编号 \(\leq\) 和 \(\geq\) 某个值时的连通性。每次查询尽量往上跳,直到跳不动,在两棵树上各得到一棵子树。要看两棵子树中是否有相同的点。
用 DFS 序转化,两棵子树的限制各作为一维,跑二维偏序即可。
Kruskal 重构树有两种构建方法:
- 按边建。把每条边的权值设为它所连两点权值的 \(\min\) 或 \(\max\),然后建。
- 按点建。从小到大 / 从大到小加入每个点,把它连接的小于 / 大于它的点所在的未连通部分连通。
都是自底向上建。[都要用并查集来维护。]
2024.11.16
6. P4606 [SDOI2018] 战略游戏
无向图摧毁单点影响连通性,显然点双。建圆方树,对每个询问建虚树,要在虚树上统计路径上的圆点个数;只需要在圆方树上预处理根到每个点的圆点个数(我之前(2024.11.16 之前)的代码里是 dis)就很好处理了。注意为了不重不漏,DP 状态的值不统计当前结点是否是圆点。注意最后要加上根的贡献(是否是圆点),并减去询问给出的点的数量(这些点不能摧毁)。
二次排序建虚树,不一定要把 \(1\) 丢进去,也不要习惯性地把 \(1\) 丢进去。二次排序建虚树最后 \(h_1\) 就是虚树的根。
我还不会单调栈建虚树。
2024.11.16
7. P2495 [SDOI2011] 消耗战
虚树 + 树形 DP。板子。
注意建虚树前后 \(k\) 可能会变(丢了个 \(1\) 进去),而且点的顺序会被打乱(按 DFS 序排序)。
注意虚树上的边不需要用倍增(树上 ST 表)或者其他数据结构来求那条路径上的边权的 \(\min\) 来作为权值,而只需要在树上算点到根的最小边权来作为这个点的点权,用点权来 DP 即可。
2024.11.16
8. CF487E Tourists
用圆方树的原因:不能经过重复的点。[在一个点双内,任意两点之间都有至少两条不相交(没有公共点)的路径。] [一定可以找到一条经过的点不重复的路径,使得开头进点双,结尾出点双,且能走过点双中一个给定的结点;这可以感性理解,我还不会证。]于是每个点双的贡献就是它里面的最小点。
考虑单点修改怎么办,一个圆点修改要改与它相邻的方点的权值,有可能会有很多方点。一个思路是根号分治,不知道能不能过。
有另一个更简单且有启发性的思路:单点修改影响相邻点的解决方案:每个方点只记它的子结点的权值 \(\min\),那么每个圆点修改时就只需要考虑父结点,对每个方点用 [multiset] 来维护即可。但注意查询时如果 LCA 是方点,还要考虑 LCA 的父亲的权值。这样做的正确性思考查询的是什么就可以说明。
参考:题解 CF487E 【Tourists】 - 洛谷专栏 (luogu.com.cn)。
2024.11.16
9. P7515 [省选联考 2021 A 卷] 矩阵游戏
感觉这道题很有启发性:对于这种信息不全的倒推题,可以先不管某些条件(本题中是范围),钦定一些信息来推出所有的信息,再调整使得不破坏已满足的限制且满足尚未满足的限制。
本题使用差分约束。注意为了满足差分约束的条件(最短路的 [三角形不等式]),我们需要根据奇偶变一些符号。
注意差分约束的最短路初始化阶段的写法,具体见我代码。要用 SPFA,要判负环,负环即无解。
2024.11.16
10. CF888G Xor-MST
对于稠密图(如:完全图)的最小生成树,Boruvka 算法是一种普遍适用的思路。然而不必拘泥于此算法的过程,而可以利用此算法的正确性来灵活地进行类似的贪心。
本题可以直接在 01-Trie 上贪心解决,具体是类似 01-Trie 合并的思路,但本题中可以不合并,直接左边的在右边询问。可以把所有数排序,那么一棵子树对应一段区间,更容易实现。
参考:题解 CF888G 【Xor-MST】 - 洛谷专栏 (luogu.com.cn)。
2024.11.16
11. AT_cf17_final_j Tree MST
看洛谷题解区说可以用 Boruvka 算法。
除了 Boruvka 算法,稠密图的最小生成树还有一种做法,就是分割 边集。(也许要求 [边权为正])把边集分成若干个子集,[使得每个子集里面的边带着的点都连通](不知道有没有这个要求)。把每个子集的最小生成树的边都拿去跑最小生成树,就能得到整个图的最小生成树。证明可以用反证法。
对于本题:拆树上两点距离,枚举 LCA,在每个 LCA 处处理每个部分之间的边,用点分治枚举 LCA 以保证复杂度。最后把每次分治时留下的边都拿到一起跑 Kruskal 即可。
参考(分割边集的结论、证明以及本题做法):【题解】AT3611 Tree MST - 洛谷专栏 (luogu.com.cn)。
2024.11.16