あまりに短い夏だけで何を残しているのかな?|

LHLeisus

园龄:1年5个月粉丝:11关注:26

图论 notes

P6628 [省选联考 2020 B 卷] 丁香之路

欧拉回路 + 贪心 + 最小生成树

这个题我花了一下午才理解,题解几乎都看不懂!实在是菜啊。菜就多练

显然 \(n\) 个点没法一块求,而 \(n\le 2500\),考虑枚举每一个终点 \(t\)

之后我们要求的就是从 \(s\)\(t\),添加一些边,把 \(m\) 条路径至少都经过一次的最短距离。如果换成只过一次不就是欧拉路径了吗,其实这还是个欧拉路径。一条路径走了 \(k\) 次,就相当于在原图(只有 \(m\) 条边)上加了 \(k-1\) 条同样的边。但是欧拉路径的条件不如欧拉回路好处理,所以我们在起点和终点之间添加一条无权的边,就变成了求欧拉回路。

现在的图边是不完整的,所以我们可以从点入手。欧拉回路的条件是所有点的度数都为偶数。我们不妨将奇数度数的点(简称奇点好不好)之间连边使它们度数成为偶数。而由于本题的特殊边权(端点编号之差的绝对值),显然相邻的两个奇点相连[1]最优。而通过简单的画图(其实是我不会证)可知奇点的的个数一定是偶数,所以这样可行。

还是没做完。现在我们可能得到的是许多联通块,要把他们连在一起。那么最小生成树应该可以很好的解决这个问题。为了保证是回路,所以每条边应该加两次,一来一回。每条边的两端点还应该是相邻的(不考虑度数为零的点)。

代码挺好写的一集


P2272 [ZJOI2007] 最大半连通子图

缩点,强连通分量,拓扑序上 dp

dp 的部分感觉完全想不清楚,dp 还是太弱了,啊啊啊啊啊——

首先同一个 SCC 里的点一定满足要求,那就先缩点,得到一个 DAG,然后就转化为了求最长链的长度和其数量。根据拓扑序执行动态规划,其实挺基础的,跟最短路计数神似,为啥我没想到,乐死我了:-D

细节比较多,首先缩点之后的图可能会有重边,会重复计算答案。加边的时候去重太麻烦了,记录每个点是从哪个点转移而来的,每次转移的时候判断一下就可以了。

其次缩点完其实已经是拓扑序的倒序了,不需要再开队列跑一遍拓扑了,直接倒序循环,比较好写。

代码


P5058 [ZJOI2004] 嗅探器

一张简单无向图,给出两个点 \(a\)\(b\),找出可以把这两个点所在点双分开的编号最小的割点。

这个题很快读懂了,但是完全不会写,感觉还是没有思维,练啊!!

先说做法:

从所给的其中 \(a\) 点开始执行 tarjan,正常找割点,若 \(dfn[\text{割点}]\le low[b]\) 则合法,否则舍弃。输出编号最小的即可。

正确性也比较明显。从 \(a\) 开始相当于固定了一个点,只用考虑另一个点即可。显然,割点和 \(b\) 需要在一个子树内。如果割点和 \(b\) 不在同一子树内,此时 \(low[b]\) 要么为 \(0\),要么小于 \(dfn[\text{割点}]\);如果在同一子树内,正确性显然。最后输出时记得判断割点是否等于所给点,这样是不合法的。


AcWing 355 异象石

询问树上几个关键点之间路径并集的总长度,有增添、删除关键点操作。

这题很怪,又很巧妙,这在于用了一个奇妙的性质:

对于一些关键点,他们之间路径并集的总长度就等于把他们按照 dfs 序排序,相邻(首尾也算)两点之间的距离和的一半

比如说:

(图丑见谅)

图上标黑的是关键点(编号即为 dfs 序),我们所求应该是 \(20\)

现在将他们排序,得到 \(2,5,6,9\),之间的路径长度分别为 \(16,9,8,7\),和为 \(40\),一半正好是 \(20\)

那就做完了,求 LCA,然后关键点丢到 set 里维护即可。

三倍经验:P3320 [SDOI2015] 寻宝游戏CF176E Archaeology


P5236【模板】静态仙人掌

毒瘤。

因为是仙人掌,所以建出狭义圆方树。

因为保证了图是仙人掌,所以我们可以基于割边 Tarjan 来建立圆方树:

  • 找到一条割边时,保留这条边作为圆方树上的一条“圆圆边”。

  • dfs 回溯时,再次遍历每个出边。如果 \(dfn_u<dfn_v\) 并且 \(fa_v\neq u\),这就说明找到环了,于是新建方点,依次连边。

圆方树最重要的就是点权的设置了。在这里,每个方点和父亲的权值是 \(0\),而方点和儿子的权值设置为儿子到该方点父亲的最短路。这个在建方点时可以用前缀和求出。

之后求个 LCA,如果 LCA 是圆点那没啥说的,方点的话需要算一下走到 LCA 之前到达的圆点是哪两个,然后求这两个点之间的最短路,依然是用之前的前缀和。我写的倍增,限制深度一直小于 LCA 往上跳就好了;树剖应该也可以,跳到最后和 LCA 在一条链上就用 \(dfn_{lca}-1\)

代码。但是不知道为啥我一定要开 longlong 才能过,有的题解就没开。

题外话。其实我们也可以基于割点 Tarjan 来建树,不过要麻烦一些。最好想的就是找到割点时判断一下点双大小,如果大于 \(2\) 再建立方点。此外权值好像十分棘手。所以我们需要在 Tarjan 过程中的 else if 处,也就是非搜索树边时记录这条边权,记在出点上。之后就十分类似了。

你会发现这两种方式在仙人掌图上建出的树是一样的,然而在一般图上是不一样的。好像……并没有什么用?


P4630 [APIO2018] 铁人两项

这是一般图,所以建广义圆方树。基于割点 Tarjan,我们对于每个点双都建一个方点就行了,很简单。

然后注意到,同一点双里的点总是能找到一条路径经过点双里的任意其他点再去往点双外的点。所以固定了两个端点以后,中间点的集合就是两点间所有方点相邻的圆点减去两个起点。那么我们让方点的点权为所在点双大小,圆点点权为 \(-1\),则答案为两点之间的点权之和

比较简单的统计方法就是,统计每个点的贡献,也就是经过它的路径条数与点权之积。注意方点不可作为路径端点即可。

代码


CF487E Tourists

写起来感觉友好多了(

依旧是建立广义圆方树,然后考虑一下点权。圆点肯定原来是啥现在还是啥,方点呢?如果设置成连接的所有圆点的 min,那修改起来就要炸了。所以设置成它儿子圆点的 min,这样修改是 \(\operatorname{O}(1)\) 的,然后都丢到 multiset 里面,修改的时候删一个旧的加一个新的,再看最小值就行了。之后树剖线段树板子,别忘了 LCA 是方点的话再查一下 LCA 的父亲圆点。

代码


P4606 [SDOI2018] 战略游戏

这个题好啊,很好啊(赞美)

还是广义圆方树的题目。可以符合条件的点都是割点,然后如果只有两个点,对应到圆方树上就是两点之间路径上的圆点数量。然后我们让圆点和父亲的连边权值为 \(1\),方点则为 \(0\),就转换成了两点间路径长度。换成多个点,就是异象石,把 dfs 序拿出来排个序就好了。最后注意如果 dfs 序最小的那个 LCA 是圆点的话,其实是没有计算它的,所以特判一下。

代码


P5022 [NOIP2018 提高组] 旅行

P5049 加强版

树就不用说了,大力 dfs。对于基环树,观察到肯定有一条边是不经过的,所以原版直接枚举这条边,然后正常 dfs 即可。

对于加强版,显然“回溯”只能使用一次,那考虑在什么时候回溯就行了,整个框架还是 dfs。

显然只有出边在环上才要考虑回溯,然后如果环上出边的终点字典序不是所有出边里最大的的肯定不回溯;如果是最大的,那还得考虑一下回溯以后走的那个点和这个点的字典序大小关系。如果回溯以后第一个走的点比现在这个大,那还不如走这个了。

所以回溯只有一种情况:当前环上的出边的那个终点的字典序最大,并且大于回溯后走的第一个点。

代码


[NOI2013] 快餐店

实际上是要求一个类似直径的东西,然后答案就是这东西的一半。

先考虑经过环的。还是经典套路,环上有一条边是不会被经过的,我们枚举这条边,然后断掉求直径,所有直径的最小值就是答案。但是显然不能直接枚举,考虑断环成链,然后复制一倍,然后设 \(sum[i]\) 表示在这个序列上边长的前缀和,\(d[i]\) 表示从环上 \(i\) 这个点拓展出去的子树内的最长路径,答案就是 \(d[i]+d[j]+sum[j]-sum[i]\)\(i\le j,j-i+1\le n\))。然后拆成 \(d[i]-sum[i]\)\(d[i]+sum[i]\),线段树维护最大值(类似区间最大子段和,查询要重新 pushup,可以重载加法),每次查询一段长为 \(n\) 的区间,然后取 \(\min\),就得到了经过环的答案。

但是如果有一条超长的链不经过环,那答案应该是这个。所以再算出每个挂在环上的子树的直径长度,和刚才的答案取 \(\max\)

个人理解:线段树维护最大值的原因是要保证这是直径,因为题目要求“距离最远的顾客”。而题目还要求“距离最近”,由于快餐店的位置是我们选择的,所以就取 \(\min\),保证最近。而最后子树里的直径又要取 \(\max\) 的原因是因为这个不是我们可以改变的,无论快餐店选在哪里它都是最长的,就只能取 \(\max\)


[ZJOI2011] 营救皮卡丘

神秘上下界最小费用可行流

先说建图方法吧:

  • 拆点,分为 \(in_i\)\(out_i\),并且在其间连一条容量为 \([1,inf)\)、费用为 \(0\) 的边。

  • 执行 Floyd 算法,记 \(f_{i,j}\) 表示从节点 \(i\) 到节点 \(j\) 经过编号不超过 \(\max(i,j)\) 的节点的最短路,求出 \(f_{i,j}\),然后对每个 \((out_i,in_j)(i<j)\) 连一条容量为 \([0,inf)\)、费用为 \(f_{i,j}\) 的边。

  • 建立超级源汇 \(s,t\)\(s\)\(in_0\) 连容量为 \([k,inf)\) 的边,每个 \(out_i\)\(t\) 连容量为 \([0,inf)\) 的边,费用都是 \(0\)

  • 最后跑一遍最小费用可行流即可。

题解里说各种转换成路径覆盖,其实我不是很懂,这里用自己的话简单解释一下。

首先到达 \(i\) 一定得经过 \(0\sim i-1\),而最终要到达 \(n\),也就是所有点都要经过至少一次,拆点即可。

接着发现,原图的边是没用的,因为不一定满足要求。而只能经过小于某个编号的点让我们想到了 Floyd,数据范围也允许。所以魔改一下,建出新图。

  • 这里其实有个小问题,就是这样表面上看是不“往回”走的,也就是不去编号小的点。有没有可能我往回走一步更优呢?实际上最终目的都是要到达编号大的点,中间过程不需要考虑,或者说已经在执行 Floyd 的时候考虑到了,记在了 \(f\) 数组里。

P1600 [NOIP2016 提高组] 天天爱跑步

2024.10.8

这个题,四月份刚做的就忘了,我是傻逼吗。

枚举每条路径肯定是没有前途,那就直接 dfs,在每一个观察员的位置算出贡献。对于当前点 \(u\),符合以下任意一个条件的路径会对其产生贡献:

\[dep_{s_i}-dep_u=w_u\Rightarrow dep_{s_i}=w_u+dep_u\\ dist_i-dep_{t_i}+dep_u=w_u\Rightarrow dist_i-dep_{t_i}=w_u-dep_u \]

所以在 dfs 过程中,开两个桶。如果当前点是起点或者终点,我们就把对应的桶的下标增加 \(1\)

这只是大体的想法。你会发现那些压根就不经过当前点的路径贡献是会出现在当前的桶里的。这包括两种,在子树外和子树内。注意到在子树外的贡献从第一次遍历到当前点到离开当前点,一直在桶里,所以我们可以计算第一次遍历到和回溯时的差值,这才是真实的贡献。而子树内的更好解决,只需要在每个点记录以当前点为 LCA 的路径,在离开当前点时减去贡献就可以了。另外,如果一条路径对其 LCA 可以造成贡献,那我们要提前减 \(1\),不然在 dfs 中会被两个桶都算一次。注意第二个桶下标可能会有负数,要加上偏移量。


  1. 如何连接两个奇点呢?直接连好像很对,但是考虑以下情况:\(1\)\(3\) 是奇点,最终 \(1\)\(2\) 不在一个联通块里,直接连的话会重复计算 \(1\)\(2\) 的距离。这时候再回去看边权的定义,发现两个点直接连接和把中间所有相邻的点都连边是等价的(\(1\to3=1\to2\to3\),总的边权都是 \(2\))。而后者可以减少联通块个数,减少重复计算,所以我们选择后者。 ↩︎

posted @   LHLeisus  阅读(15)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
展开