图论杂项
树的直径
两次 \(\operatorname{bfs}\) 可以记录路径,但边权必须非负。
\(\operatorname{dp}\) 边权任意,但不方便记录路径。
性质: 树的直径不唯一,所有直径必定相交于中心处(可以为一条边或者为一个点)。
维护动态加点直径,可见 QOJ8235,只需要在可能的多个端点集合中,任意维护两个端点即可,每次加点,就是求出三个距离,比较一下即可。
P6845 [CEOI2019] Dynamic Diameter
考虑如何刻画直径,如果是两次 dfs 那样显然是不适用于多次查询因为没有一个明确的表达式。
我们可以按照树上 dp 那样子方法来求解,因为那有一个明确的表达式。于是即求 \(\max \{\ dep_u+dep_v-dep_{lca} \}\)。待修改的多次查询启发我们用数据结构维护,于是将树转化为欧拉序,于是 \(dep_{lca}\) 就转化为 \([\min(l_u,l_v),\max(r_u,r_v)]\) 中的最小值。考虑到减去的最小值,于是我们只需要求 \(\max\limits_{i\le j\le k}\{a_i+a_k-a_j\}\)。
这可以用线段树维护,我们可以拆开维护再合并,维护最大的 \(a_i-a_j\) 和 \(-a_j+a_k\) 还有区间 \(a_i\) 的最大最小值。时间复杂度 \(O(n\log n)\)。
CF842E Nikita and game
我们要维护的就是支持动态加叶子节点的树的所有直径端点。
一个很显然的思路就是提前建立整颗树,然后预处理一下以便后面可以快速查询两点距离。然后动态加点删点,维护直径集合。类比结论:到树上某点距离最远的点一定是直径端点之一。我们可以得到,到新加入叶子节点最远的点也是直径端点之一。
于是不断 \(\operatorname{check}\),新入叶子节点无法构成直径端点直接舍弃。如果可以成直径端点,且直径长度不变就直接加入集合中。如果长度增加就舍弃那些不能与该叶子节点构成新直径的端点。这样子时间复杂度是 \(O(n^2)\) 的。
观察到上述做法复杂度高的原因是可能出现多条直径/直径端点。于是这里要用到一个很关键的性质:
树的所有直径必定交于一点/一条边。
这条性质提供了一个让我们把直径端点划分的基础。
中心为边
假设目前是交于一条边,那么我们只需要维护这条边两端的直径所有端点集合,分别记为 \(L,R\)。
每次 \(\operatorname{check}\) 的时候直接从 \(L,R\) 两个集合中分别任选一点算距离即可。
然后选择是否加入对应的 \(L/R\) 集合内即可。遇到接到了 \(L/R\) 集合端点的情况,我们直接舍弃整个集合即可。
中心为点
但是当交点是一个点的时候就很难办了,因为会出现不止两个集合。
但是其实如果算叶子到某直径端点的距离,两个集合还是可以用的。因为叶子必然只能在一个子树内,而两个集合肯定会分出至少两个子树。
可以这个删除就难办了,考虑新来了一个叶子节点接在了某个直径上,和它同一集合的可能会舍弃,也可能可以构成直径。不能暴力 \(\operatorname{check}\) 吧。
有一个很神仙的做法就是,观察到在中心为点的情况下我们如果往一个直径端点上加点,直径必然会移动到一条边上(具体来说就是往该子树的边上移动)。此时又会变成严格两个集合。于是我们每次加点都往 \(L\) 内加点,保持 \(R\) 是一个完整的子树内的点。然后当 \(R\) 内被接上了一个点,那么我们直接舍弃 \(R\) 即可。如果 \(L\) 上被接上了一个点,那么我们可以暴力 \(\operatorname{check}\) 是否舍弃,还是保留。根据上面的观察,保留之后的点必然会从 \(L\) 集合迁移至 \(R\) 集合。而如果 \(R\) 集合不符合要求是直接会被舍弃的,所以综上每个点只会被 \(\operatorname{check}\) 至多一次(也就是迁移那次)。
注意我们并不需要维护中心具体在哪,因为点和边的做法可以统一。
所以时间复杂度 \(O(n\log n)\),瓶颈在于求 lca。
UOJ841. 龙门探宝
注:本题解为给定树版本,如果在原题会交互次数超过限制。
考虑对于 \(r\) 进行扫描线,对于每个左端点 \(l\) 维护区间 \([l,r]\) 的直径。
考虑 \(r\to r+1\) 的过程,新加入一个 \(r+1\),有一个暴力算法,可以发现 \(r+1\) 能更新的一定是一段后缀,所以我们暴力往前更新,同时遇到不合法的话立刻退出。这样子是 \(O(n^2)\) 的,不过可以通过随机数据的 subtask。
然后考虑以下结论:

我们可以用 std::set 来维护连续段,每次把整个段中的全部直接更新即可。
图上计数
P1989 无向图三元环计数
对于无向图三元环计数,我们可以以度数为第一关键字,以编号为第二关键字对于原图上的无向边进行定向(由小连到大)。这样子我们连出来的就是一个 DAG。
我们考虑在三元环的最小点处统计贡献,然后我们枚举 \(u\),再枚举 \(u\) 的出边到达点 \(v\),接着枚举 \(v\) 的出边到达点,看看是否存在 \(w\),使得图上有 \(u\to w\) 边。具体实现的话,我们在枚举到 \(u\) 的时候给它的所有出边对应点的 \(from\) 数组设置为 \(u\),然后 check 一下是否有 \(from_w=u\)。
分析一下时间复杂度,我们可以统计每条边对于时间复杂度的贡献。这里统计边 \((u,v)\)(变量含义同上一段),每条边贡献了 \(out_v\)。我们断言每个点的出度都是 \(\le \sqrt m\) 的。对于原图上度数小于 \(\sqrt m\) 的点 \(v\),这显然成立。对于度数 \(>\sqrt m\) 的点,我们可以发现它连向点 \(w\) 的度数必然是大于等于该点的,这样子的 \(w\) 不超过 \(\sqrt m\) 种,也就是说 \(out_v\le \sqrt m\)。注意区分度数和出度数。
每条边最多贡献 \(O(\sqrt m)\),于是时间复杂度是 \(O(m\sqrt m)\) 的。
精细估计,最严的上界是 \(\sqrt {2m}\) 的,而不是 \(\sqrt m\),因为 \(\sum deg_i=2m\)。
LOJ191. 无向图四元环计数
还是和三元环相同的方式给图定向。
然后可以发现一个四元环是由某点出发往外连了两条无向边,然后从无向边的两个终点再往外连两条有向边,两条有向边交于一点,这一点就是环上的第四个点。为了防止重复要求第四点的 \(rk\) 大于第一点。
考虑如何快速实现以上过程,我们只枚举其中一半的边,通过一条无向边加一条有向边找到第四个点,然后给该点的计数器加一,最后该点的贡献就是 \(\dfrac{cnt_i\times(cnt_i-1)}{2}\)。
注意必须先枚举无向边,再枚举有向边,否则复杂度不正确。每个点出度 \(\sqrt m\),所以在第一次枚举无向边的时候会枚举到 \(m\) 条,每条边指向的点贡献 \(O(\sqrt m)\),于是总的时间复杂度 \(O(m\sqrt m)\)。
如果先枚举有向边的话,是 \(m\) 条,再枚举无向边又是 \(O(m)\),复杂度就不对了。
需要注意,这种统计之下 \((a,b,c,d)\) 和 \((a,c,b,d)\) 是两个不同的四元环。
QOJ6354(\(K_4\) 计数)
\(K_4\) 就是四元完全子图,类似于三元环计数,给图定向。
预处理每个点 \(i\) 的出边指向点组成的 std::bitset \(a_i\)。由于出边个数是 \(O(\sqrt m)\) 的,所以对于每个 \(a_i\) 其中 \(1\) 的个数也是 \(O(\sqrt m)\) 的。故对于 bitset 单次的操作时间复杂度是 \(O(\dfrac{\sqrt m}{\omega})\) 的,下面会用到。
把 \(K_4\) 拆分成一个点连一条有向边向三元环上的三个点。这个点是 \(\rm rk\) 最小的点,外层枚举这个点,内层枚举这个点的所有出边,这两层的复杂度是均摊的 \(O(m)\)。
由于每个点出边数是 \(O(\sqrt m)\) 的,所以我们会得到 \(O(\sqrt m)\) 个关键点,我们需要对于这三个点求出三元环的个数。之前已经有 \(a_i\) 了,现在我们需要求出 \(b_i\),也就是集合内相邻点,直接对于所有关键点的 \(b_i\gets a_i~\rm{and}~S\) 即可。这一步复杂度是 \(O(\sqrt m\times \dfrac{\sqrt m}{\omega})\) 的,配合上外层一共是 \(O(\dfrac{m^2}{\omega})\) 的。
我们枚举所有关键点的出边 \((x,y)\),注意这一步配合上枚举 \(\rm rk\) 最小点的所有出边(最外层枚举的边),时间复杂度是 \(O(m\sqrt m)\) 的,因为这几步放在一起等价于三元环中的连续枚举两条有向边。然后贡献就是 \(b_x~\rm{and}~b_y\) 中 \(1\) 的个数,这一步是 \(O(\dfrac{\sqrt m}{\omega})\),所以总复杂度还是 \(O(\dfrac{m^2}{\omega})\) 的。
当然上面做法中空间开销很大,其实可以不开 \(a,b\) 数组,直接开一个 \(O(\sqrt m)\times O(\sqrt m)\) 的 std::bitset内部重标号即可。
Gym104819F Four K3
如果我们对于每条边都统计出其被多少三元环所包含,那么一个三元环的贡献就 \((a_i-1)(a_j-1)(a_k-1)\)。
可是这个贡献会算多,因为可能三条条边延伸出去的三个第三点之中会重合。
现在需要讨论的就是到底是三个都重合,还是有其中的两个重合。
对于三个都重合的情况,画个图可以发现这是一个 \(K_4\),计算其个数扣除即可。
其中两个点重合的时候,可以发下就是一个 \(K_4\) 挂上了一个三元环。求出每条边被 \(b_i\) 个 \(K_4\) 所包含。贡献就是 \(b_i(a_i-2)\)。之所以要 \(-2\) 是因为你会发现那个 \(K_4\) 在这条边上贡献了两个三元环需要扣除。
最后只需要求每条边被多少 \(K_4\) 所包含即可。
QOJ6441. Ancient Magic Circle in Teyvat
使用容斥原理之后,可以发现题目要求的就是带着容斥系数的钦定了 \(i\) 条边为红色 \(K_4\) 计数。

最短路
差分约束
利用图上最短路的三角形不等式,对于边 \((u,v,w)\) 有 \(dis_v\le dis_u+w\)。
所以可以用上述式子进行不等式组 \(x_i-x_j\le c_k\) 的约束。
如果要求 \(x_i\le y\) 的字典序极值,那么就新建一个超级源点距离设置为 \(y\)。
最短路图
UVA1416 Warfare And Logistics
先考虑最基本暴力,删除每一条边然后每一个点跑一遍最短路。可以对每个点建出最短路图,如果不在图上的边被删除就不要跑最短路了。
JOI2016C 鉄道運賃 (Train Fare)
法一:最短路图
法二:我们可以发现如果增加一条边的边权后,还希望修改后的最短路长度与修改前相同的话,那么必然修改后不会经过这条边。于是等价于将这条边删去。
但是删边操作后不容易修改最短路,可以考虑倒序操作,将删边变成加边,也就是先把所以操作边删去,再倒着扫描加进去,这样就好办多了。我们只需要在加入一条边后更新一下最短路即可。更新如果全局重做的话复杂太高,可以找出哪些点受影响,也就是先找到被操作边两端连接点,用最短路小的去更新大的,如果更新失败直接返回,否则将更新后的点入队。然后 bfs 一遍去继续更新。注意更新条件为如果更新后路径长度为最短路长度,才能更新,否则更新后也不是最短路对答案也就没有影响,不如等到最后可以是最短路了再一并更新。
Kruskal 重构树
刻画关于图上边权极值相关的联通性。
与 Kruskal 过程相同,只不过是每次取出一条边后对于两个端点所在并查集的根新建立一个父节点,连接一条边的两个点,其点权等于边权。
性质
- 若按边权升序排序则构建出来的是大根堆,反之为小根堆
- 若按边权升序排序,则 \(\operatorname{lca}(u,v)\) 代表原图中 \((u,v)\) 路径上最大边权的最小值。反之则为最小边权的最大值。
- 叶子节点为原图节点,其余节点为原图中的边。由此也可以推断出(不是森林时)会多 \(n-1\) 个的节点,共计 \((2n-1)\) 个节点。
Tips
- 注意若原图不连通,构建出来的是森林
- 初始化并查集注意是 \(2n-1\) 个点。
应用
-
\((u,v)\) 路径上最大边权的最小值 P1967 [NOIP2013 提高组] 货车运输
-
从 \(u\) 出发,只经过边权不超过 \(x\) 的边可以到达的点 P7834 Peaks,此时这些点构成重构树中的一个子树。
P4899 [IOI2018] werewolf 狼人
对于 \(\min\) 和 \(\max\) 分别建立 Kruskal 重构树,然后我们可以倍增跳祖先,祖先子树内的叶子节点都是可达点。我们只需要对于两颗子树判断叶子节点有没有交即可,对于一个点我们可以变成 \((dfn_1,dfn_2)\) 然后离线二维数点即可。
ARC098F Donation
这题和 P7831 [CCO 2021] Travelling Merchant 好像啊。这种看起来很像 dp,但是由于图上有环很难直接 dp 的题目都可以通过按照一定顺序加边/遍历点来达成无环 dp 结构。
首先有一个性质,就是如果会多次经过某个点,在其最后一次经过的时候捐赠肯定是更优的。因为这可以让他在中间过程中有更多的钱。
可以发现 \(a_i,b_i\) 双约束有点难办,所以我们考虑将这个过程倒过来,令 \(c_i=\max(0,a_i-b_i)\),代表我们进入 \(i\) 所需的最少钱。并且第一次到达 \(i\) 点,可以获得 \(b_i\) 元,求最少的起始金额。
点权约束可以想到 Kruskal 重构树,可以发现我们的路径一定是从一个叶子出发往上走,每到一个节点 \(u\),就往下将其所有子树节点的 \(b_v\) 获取再往上走,而且由于 \(c_u\ge c_v\),所以只需要考虑 \(u\) 点一个点的约束即可。设 \(s_u\) 表示 \(u\) 子树内的 \(\sum b_v\),\(dp_u\) 表示 \(u\) 子树至少需要的钱数。
于是对于 \(u\),我们需要决策初始是从哪个地方上来的。这会有一个约束 \(\max(dp_v,c_u)\),遍历完其他子树后还会得到 \(s_u-s_v\) 的的钱。所以 \(dp_u=\min(dp_u,\max(dp_v,c_u)+s_u-s_v)\)。
树的重心
性质:
-
某个点是树的重心等价于它最大子树大小不大于整棵树大小的一半。
-
树至多有两个重心。如果树有两个重心,那么它们相邻。此时树一定有偶数个节点,且可以被划分为两个大小相等的分支,每个分支各自包含一个重心。
-
树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。反过来,距离和最小的点一定是重心。
-
往树上增加或减少一个叶子,如果原节点数是奇数,那么重心可能增加一个,原重心仍是重心;如果原节点数是偶数,重心可能减少一个,另一个重心仍是重心。
-
把两棵树通过一条边相连得到一棵新的树,则新的重心在较大的一棵树一侧的连接点与原重心之间的简单路径上。如果两棵树大小一样,则重心就是两个连接点
-
一棵树的重心一定在根节点所在的重链上
CF1292D Chaotic V.
首先根据经典结论,题目中的 \(P\) 点即为重心。
本题为找重心的一大经典方法,也就是在更优的情况下每次都往最大的子树走一步,直到抵达重心。
思考一下判定重心的结论,假设树的大小为 \(n\),就是以某个点为根的时候,所有子树的大小都 \(\le \dfrac{n}{2}\)。注意这里的大小 \(n\) 是题目中所给关键点的个数,而非算上了那些虚点,因为虚点唯一的作用就是增加了长度,而树重心的位置和边权无关,只与点权有关,具体证明可以看 P3345 的第一篇题解,注意那篇题解里有个笔误,就是他说和点权无关,实际是和点权有关的。
我们初始从 \(1\) 节点出发,然后开始统计每个子树的大小,由题意可得每个数所在子树都是以它的最大质因子为根的子树。
于是我们统计每个质因子作为最大质因子出现的次数,设出现次数最大的质因子为 \(p\),如果 \(2\times sz_p >n\)(此时不符合上述所说的结论),我们就往 \(p\) 所在子树内走一步,动态更新答案(答案要增加 \(n-sz_p\),同时减小 \(sz_p\))。
每次移动之后修改关键点所在子树的根的信息,比如从 \(1\to p\) 之后,\(p\) 子树之外的关键点所在子树的根就变成了 \(1\)。同时 \(p\) 子树内的关键点的子树根也变了,如果 \(p\) 的幂次为 \(1\) 就变成了次大质因数,否则就还是 \(p\)。注意前后两个 \(p\) 数字大小相同,但是点不同。
所以每次移动 \(rt\to rt'\) 之后,\(rt'\) 外部的关键点所在子树的根就变成了 \(rt\)。我们要让 \(sz_{rt}\gets n-sz_{rt'}\)。对于原本 \(rt\) 子树内的关键点,它们所在子树内的根变成了可重质因子集合中的次大,同时把最大的踢出去。
同时修改每个点的子树根信息。由于每个数的可重质因子个数不超过 \(O(n\log V)\),所以时间复杂度为 \(O(n^2\log V)\)。
LCA
一些小技巧:
-
树上三点到一点距离和最小,该点即为求出两两 \(lca\),不重合的那个。
-
判断树上路径 \((s,t)\) 和 \((a,b)\) 是否相交,就是看是否一条路径的顶点(lca)在另一条路径上。
DFS 序求 LCA
\(u\) 不是 \(v\) 祖先,查询二者之间 dfn 序最小的节点,它的父亲就是 \(\rm LCA\)。否则查询 \([dfn_u+1,dfn_v]\) 之间的。同样的情况一也可以推广到第二种情况的那种查询。
最小生成树
P3623 [APIO2008] 免费道路
考虑最直接的做法,一直加 \(k\) 条鹅卵石边,然后再加水泥边,这显然可能导致最后连不出来一棵树。问题在于我们如果在多条鹅卵石边中选出正确的 \(k\) 条边,使得尽可能让水泥边可以连成一棵树。
考虑进行水泥边优先的 Kruskal,那么在这颗生成树中的加入的鹅卵石边就是一些必选边。其他鹅卵石边显然是效果和必选鹅卵石边重复或者可以被水泥边替换的。于是我们再来一次变式的 Kruskal,先加入必选鹅卵石边然后再加入鹅卵石边直到数量达到 \(k\),最后再加入水泥边。
【UER #1】DZY Loves Graph
动态维护带撤销的最小生成树。
如果只是加边的话,因为加入的边单增,所以我们当它可以连成树的时候记录答案,此时答案就不变了。
带上删除操作,我们可以可持久化并查集,每次删除直接回退就行了,删除的次数最多为加边次数 \(O(m)\)。同时记录每个状态的是否连成树了,回退到某个状态如果已经连成树了就直接输出那个状态的答案,理由和上面那个一样。
如果还有回退操作,可能会让我们上述的均摊变得不正确,我们可以离线下来,如果删除后一个操作不是撤回就删除,如果是撤回这次就不删了,直接统计答案。反正下次就撤回来了。
AT Tree MST
Boruvka 模板题。
每轮为每个联通块寻找一条最小的向外延伸的边,也就是我们为每个点都找到一条边连向不在同一联通块的点,然后对于同一联通块内的点取 \(\min\)。最多进行 \(\log\) 轮。
本题中考虑为 \(u\) 寻找一个 \(v\) 最小化 \(a_u+a_v+dis_{u,v}\),考虑拆开对于每个点维护一个 \(val_u=dep_u+a_u\),固定这个值,我们只需要找到 \(\min val_v-2\times dep_{\operatorname{lca(u,v)}}\),考虑枚举这个 lca,然后只需要找到子树内的最小点和次小点并钦定二者不在同一联通块内即可,然后下传这个值,让子树内的点进行匹配即可。
约束类
P3243 [HNOI2015] 菜肴制作
正着考虑字典序最小是错解,因为无法保证 \(1\) 更早到达,正确的应该是走到 \(1\) 的最短路径中最优的一条。
会发现要求非常 "紧迫",因为要求以最快速度填入 \(1\),然后是 \(2\) 以此类推。
同时满足先到 \(1\) 和路径更优这两个条件,这样操作空间不大。但是如果我们建反图考虑的话,发现反正是尽可能后的放 \(1\) 所以并不着急放 \(1\), 可以随时放。这就从容许多。于是因为没有“先到 \(1\) ” 这个任务,那就可以直接字典序了。
类似 图/树上约束到达顺序题目:
P9755 [CSP-S 2023] 种树 按照 \(t_i\) 排序。
P1954 [NOI2010] 航空管制 建反图拓扑,直到必须选才选
CF1765H Hospital Queue 建反图拓扑。
[ABC304Ex] Constrained Topological Sort
在正反图上的 DAG 上跑拓扑排序递推这个式子,对于边 \((u,v)\),进行更新 \(l_v\gets l_u+1\),\(r_u\gets r_v-1\)。
把所有限制都松弛过一遍之后,就可以把这张图丢掉了。我们要求解决的就是给每个点分配一个编号使得其编号 \(p_i\in[l_i',r_i']\),这是一个匹配形式,通过对于区间使用 Hall 定理可以判定其是否有解。
构造解的话,就是我们扫描 \(l\),每次贪心取出 \(r\) 最小的点即可。时间复杂度 \(O(n\log n+m)\)。
欧拉回路
- 欧拉路径:经过联通图每条边恰好一次的路径被称为欧拉路径。
如果这条路径为回路,那么就是欧拉回路。
存在欧拉回路的图称为欧拉图,不存在欧拉回路但存在欧拉路径的称为半欧拉图。
有向图
有向图 \(G\) 为欧拉图当且仅当 \(G\) 弱联通且每个点的出度等于入度。
有向图 \(G\) 为半欧拉图当且仅当 \(G\) 弱联通且存在两个点入度分别等于出度减 \(1\) 或者出度加 \(1\),其余点入度等于出度。
无向图
无向图 \(G\) 为欧拉图当且仅当 \(G\) 弱联通且每个点度数为偶数。
无向图 \(G\) 为半欧拉图当且仅当 \(G\) 弱联通且存在两个点度数为奇数,其余点度数为偶数。网络流可解。
混合图
对于混合图有一部分边为有向边,一部分为无向边。
首先,将有向边改为无向边后图必须联通。满足上述条件后,我们只需要对于无向边定向,使得对于每个节点出度等于入度即可。
那么对于欧拉图要求每个点度数均为偶数。对于半欧拉图要求有两个点的度数为奇数其余要求为偶数,此时枚举两个点哪个是起点哪个是终点,就和欧拉图做法一样了。
给边定向是二选一模型,考虑网络流建模,建立二分图,左边为边,右边为点,每条边连向它的两个端点对于每个点都有度数约束,跑一遍最大流,如果最大流为无向图的边数那么有解,且当前流给出了一种方案,否则无解。
P3511 [POI2010] MOS-Bridges
使用网络流求解混合图欧拉回路定向。
求解
类似于当前弧优化,我们直接 dfs,同时维护下一条出边。如果一个点所有边都跑过一遍了,那么把这个点加入栈,最后栈中从栈顶到栈底是欧拉路径的顺序。回路就是任意顺序。
ZROI2999.随机
首先要识别可以观察到前两个条件是包含于第三个条件的。其次猜测如果可以符合要求那么 \(n^3\) 长度就足够了。这个时候剪枝+ dfs 应该是可以过的。不过更好的方法是欧拉回路。
把两个字符看成一个状态 \((a,b)\),然后出现连续 \(abc\),就相当于 \((a,b)\) 向 \((b,c)\) 连了一条边,可以发现符合要求的构造就是一个欧拉回路。
ZROI3049.小镇
这种有两类边的图上问题一般都是以一种最重要边的生成图来看,本题中显然是选择必经道路的生成图 \(G_1=\{V,E_1\}\),在这张图上考虑本题要求就是一个欧拉回路。我们只需考虑度数即可,欧拉回路的要求的度数均为偶数。我们需要用可选边改变一些度数使得度数为偶数。
暴力做法就是直接枚举每条可选边选不选,然后计算一下度数看看要加几条特殊边即可。
首先,思考如何最小化特殊边个数,这个时候我们转化到图 \(G_2=\{V,E_2\}\) 上面。考虑每个包含奇点(在图 \(G_1\) 中度数为奇数)的联通块。
如果有偶数个奇点我们必然可以在这个联通块内通过非必经边完成度数限制。就是考虑如果两个奇点相连,我们直接选择他们之间的边,这样子两个的度数都变成了偶数。剩下的都是奇点和偶点之间的边,由于奇点个数为偶数且奇点之间连边一次消耗两个点,那么剩下的奇点是偶数个,也就会一共连偶数条向偶点,那么偶点的总度数还是偶数,现在考虑单个偶点的度数是否还是偶数,如果有奇数个奇点连向他,我们必然可以通过调整(调换一个奇点连向的偶点)或者偶偶边(甚至通过第一类匹配掉的奇点间接相连),使得单个偶点的度数增量也是偶数。所以偶数个奇点的联通块不需要特殊边。
对于奇数个奇点的联通块,我们直接在联通块内部消耗一下,然后联通块之间两两匹配一条特殊边即可,因此特殊边个数为奇数个奇点联通块个数 \(/2\)。注意由于图上各点的总度数为偶数,因此不可能出现有奇数个上述联通块的情况。
这样子我们就完成了最小化特殊边。
接下来要最小化非必经边,这个注意题目那个表示,可以知道 \(G_2=\{V,E_2\}\) 应该是一个基环树森林或者森林。
对于树就是简单 dp 了,记录度数的奇偶性进行转移。注意奇数块由于要引入外面的一条边,我们就要加一个转移决策,同时需要用 pair 记录一下,第一比较关键字是外来边个数,第二关键字是选择的非必需边个数。
对于基环树,我们直接先随便断开某条边 dp 一下。然后强制选择这条边也就是 \(deg\oplus 1\) 一下然后再 dp 一遍就行了。
CF1583E Moment of Bloom
考虑合法的充要性。
首先,最后的图合法的必要条件是每个点所连边的经过次数之和为偶数(最后每条边都是偶数,加在一起也是偶数)。
考虑一条路径可以给路途上的每个点(除了端点)度数都加偶数(一进一出),而端点被加了奇数次。于是统计每个点作为路径端点的次数,如果有点为奇数显然是不行的,于是可以两两配对,最后的答案就是奇点个数除以二。如果每个点的度数都是偶数,我们尝试将必要条件推广至充要,方法是给出构造。如果每个点的度数都是偶数,等价于我们建立一个新图,把每条路径的端点在这张新图中连在一起,然后这张图存在欧拉回路。
那么设给出的路径端点分别是 \((s_i,t_i)\),我们求出欧拉回路之后满足 \(t_i=s_{i\bmod n+1}\),于是可以走 \(s_1\to t_2 \to t_2\to ...\to t_{n-1}(s_n)\),在 \(s_n\) 的位置我们要走到 \(t_n=s_1\),我们可以按照来时的路再反着走一遍,这样子就可以保证每条边都经过奇数次了。
上述做法给出了构造因此结论成立。不过我们的构造部分如果按照上述这么写就太麻烦了,考虑求出原图的任意一颗生成树,那么对于一条路径,直接输出两个端点在生成树上的简单路径即可。正确性显然。
时间复杂度 \(O(nq)\)。
NFLS18435.矩阵
给定 \(n 个矩阵,\)求 \((\sum\limits_{p}\prod\limits_{i=1}^n A_{p_i})_{1,1}\),\(n\le 500\)。
直接依赖于记录矩阵形态的做法,必须记录选了哪些矩阵,那么就只能状压 dp,显然复杂度不行。
考虑刻画矩阵乘法贡献到 \((1,1)\) 的路径,\((x,y)\times (y,z)\to (x,z)\)。那么必然是 \((1,s_1)\times (s_1,s_2)\times (s_3,s_4)\times\dots (s_{n-1},1)\)。这种相邻两个二元组都有一个相等的链式结构,是欧拉回路的典型形态。由于 \(s_i\in\{0,1\}\),于是我们考虑建图,有两个点 \(1\) 和 \(2\),边 \((1,2)\) 就代表选择某个矩阵的 \(A_{1,2}\),其他三种边 \((1,1),(2,2),(2,1)\) 同理。
考虑先不钦定 \(p_i\) 这个顺序,先为每个矩阵 \(i\) 确定其在贡献路径中贡献哪个位置 \((s_{i-1},s_i)\),然后按照这个二元组来连边。那么这种选择方式对应的可能的排列数量就是连出来的图中的欧拉回路的数量。
欧拉回路数量需要使用 BEST 定理,BEST 和度数有关,于是我们考虑在 DP 的时候状态记录下度数,DP 值为所有满足当前度数的方案中 \(A\) 乘积之和。之后再给 DP 值乘上可能的排列个数即可。
但是使用 BEST 定理的前提是本题存在欧拉回路,需要用度数判定欧拉回路,要求入度等于出度,发现 \((1,1)\) 和 \((2,2)\) 都自己内部抵消了,唯一的约束是 \((1,2)\) 的个数和 \((2,1)\) 个数相等。
于是我们令 \((1,1)\) 的个数为 \(a\),\((2,2)\) 的个数为 \(b\),\((2,1)\) 的个数等于 \((1,2)\) 的个数为 \(c\)。
根据 BEST 定理这样子的欧拉回路的个数为 \((a+c)!(b+c-1)!c\)。
直接记录 \(a+c\) 和 \(b+c\) 即可,计算式子中的 \(c\) 可以在 DP 的过程中钦定一条特殊的 \((1,2)\) 边就行了。
DP 的过程中按照 \(c\) 为 \((1,2)\) 边的个数算。注意当 \((a+c)+(b+c)=n\) 的时候,自然满足了 \((1,2)\) 和 \((2,1)\) 个数相等。注意还要特判掉全部都是 \((1,1)\) 的情况,因为这个时候我们 DP 钦定 \((1,2)\) 边就和这个矛盾了。
时间复杂度 \(O(n^3)\)。
基环树
对于环的两种基本处理方式:直接断环,两次 dp。或者断环为链,复制一遍,转化为序列问题。
P10933 创世纪
这一类约束型就肯定选择两次 dp。
对于两次 dp 类的基环树,我们只需要找到环上的相邻两个点即可,不需要找出整个环,可以用并查集寻找基环树森林的环上相邻点。假设某颗树是 \(s\to t\),那么就先断这条边,正常 dp 一次,然后 \(ans\gets\max(dp_{t,0},dp_{t,1})\),然后强制不选 \(t\),那么同时记录下 \(s\) 代表可以随意选。最后 \(ans\gets dp_{t,0}\)。
P4381 [IOI2008] Island
最优选择类就是断环为链了。
这种需要找到所有环上点,可以采用拓扑排序,由于环上的度数最小也只能缩小到 \(2\),所以不可能被标记。最后所有 \(deg_u=2\) 的点就是环上点了。
最后在链上就是一个简单单调队列优化 dp 就行了。
2-SAT
如果一个约束条件涉及两个变量就是 \(2-SAT\) 了,\(\ge 3\) 是 NPC 问题。
我们直接按照互推性连边即可。如果一个点的正反状态都在一个强联通分量中的话那就是无解了。否则考虑如果构造一组合法解。
策略是:对于每个点选择拓扑序大的状态节点。
由于拓扑序小的点可能推出拓扑序大的点,所以如果选择拓扑序小的点的话就可能会导致自己推导到自己的反面这种情况。 tarjan 求出来的是反拓扑序,所以我们只需要选择 \(col\) 小的节点即可。
对于字典序最小解,可以逐位确定在图上暴搜标记真假的传递,时间复杂度 \(O(nm)\)。如果是传递闭包可以做到 \(O(\dfrac{n^3}{w})\)。
3-SAT 计数:https://atcoder.jp/contests/kupc2021/tasks/kupc2021_k
NFLS13052. 仪式
给你一棵树,以及若干限制形如在以 \(r_i\) 为根的树上,\({\rm LCA}(p_{a_i},p_{b_i})=c_i\),你需要构造这么一组排列 \(p\)。
设变量 \(id_{i,j}\) 表示以 \(1\) 为根的树上,\(p_i\) 在 \(j\) 的子树内。以下连边只讲原命题,默认连上逆否命题。
首先有 \(\neg id_{i,1}\to id_{i,1}\),表示 \(i\) 点肯定在 \(1\) 子树内。
还有 \(id_{i,j}\to id_{fa_i,j}\)。和对于没有祖先关系的点 \((u,v)\),\(id_{i,u}\to \neg id_{j,u}\),这一步暴力连是 \(O(n^3)\) 的,可以用前后缀优化建图做到 \(O(n^2)\)。这是一些基本统一约束。
对于这些限制的约束,我们需要分类讨论 \(r_i\) 和 \(c_i\) 的关系。
-
\(r_i=c_i\),此时 \(a_i\) 和 \(b_i\) 在 \(r_i\) 的不同儿子子树内且至少有一个在 \(r_i\) 子树内。
-
\(r_i\) 为 \(c_i\) 祖先,\(a_i\) 和 \(b_i\) 必须在 \(c_i\) 的子树内,且不是同一个儿子。
-
\(c_i\) 为 \(r_i\) 的祖先,此时 \(a_i\) 和 \(b_i\) 在 \(c_i\) 的不同儿子子树内且至少有一个在 \(c_i\) 子树内,同时不能和 \(r_i\) 一个子树。
时间复杂度 \(O(n^2)\)。
AT_acl1_f Center Rearranging
竞赛图
性质
竞赛图缩点之后为链。
很好证明,任意两点之间都有一条边,这意味着它们必定有一个 "大小" 关系,要么两个在同一个强联通分量中,要么二者所在强连通分量有拓扑序先后关系。
还有一种证明方式就是构造性证明,考虑逐个 scc 加入。如果一个 scc 向当前所有已经加入的 scc 都有连边,那么就放到开头。如果所有已加入的 scc 都向该 scc 有连边就放到末尾。否则存在一个分界,使得一边是向它连边的 scc,一边是它向外面连的 scc,插入到分界点即可。
竞赛图中,如果存在 \(u\to v\) 的路径,但是不存在 \(v\to u\) 的路径。那么 \(in_u<inv_v\)。
竞赛图中,任意两点都有两边,这个边的定向和 SCC 的拓扑序有联系。
已知竞赛图所有点入度 \(in_i\) ,可以得到每个强连通分量包含的点。
对于 \(in_i\) 排序,每次找到前缀使得 \(\sum\limits_{i=1}^kin_i=\dfrac{k(k-1)}{2}\) 即可。
利用的是入度大小转化为拓扑序大小,且后面不可达前面。
Redei 定理:任意 \(n\) 阶竞赛图都存在哈密顿路径。
Camion - Moon 定理:强连通竞赛图一定存在哈密顿回路。
P10441 [JOISC 2024] 乒乓球
题意:构造一个 \(n\) 个点的竞赛图,使得其中三元环个数为 \(m\)。
经典结论:竞赛图三元环个数为 \({n\choose 3}-\sum\limits_{u}{deg_u\choose 2}\),其中 \(deg\) 为出度序列。
兰道定理:竞赛图度数序列合法当且仅当将度数序列从小到大排序,满足 \(\forall k,\sum\limits_{i=1}^kdeg_i\ge {k\choose 2}\)。
从上述两个结论可以看出,当度数尽可能均匀的时候,会让三元环个数增多,同时会让兰道定理更容易成立。
于是我们先让度数在满足兰道定理的情况下,尽可能不均匀分布,得到一个三元环个数很少的竞赛图。然后每次往让其更均匀一点,这样子会在三元环个数增多的情况下,保证满足兰道定理。其中初始序列是 \(deg_i=i-1\)。
将 \(\{i,i+2\}\to \{i+1,i+1\}\) 的时候,我们可以发现三元环个数会 \(+1\),如果暴力调整的话是 \(O(M)\) 的。
可以发现 \(O(n^3-(n-1)^3)=O(n^2)\),于是我们构造一组尽可能接近 \(M\) 的解,然后暴力调整,时间复杂度就是对的了。
P3561 [POI 2017] Turysta
首先不难猜测,SCC 缩点之后,某个点的答案为其所在 SCC 大小加上链上后续 SCC 大小之和。
由于对于两个 SCC \(U\to V\),\(\forall u\in U,v\in V\),均有边 \(u\to v\)。所以我们不在乎终点在哪里,只需要对于每个 SCC 内部求出最长路径即可,但是需要是以每个点为起点都找到这么一条路径。
先来一个暴力的做法,固定起点的构造。构造哈密顿路径相关,一般都考虑增量法构造路径,维护这么一条链 \(u_0\to u_1\to u_2\to \dots \to u_t\)。考虑加入单点 \(v\),如果存在 \(v\to u_0\) 或者 \(u_t\to v\) 那就直接可以连上去了(称为一类构造)。否则我们需要找到一个 \(i\) 满足存在边 \(u_i\to v,v\to u_{i+1}\),然后直接将这个点塞进去(称为二类构造)。但是如果不存在这个 \(i\) 我们不就坠机了吗?其实不是的,因为假如不满足一类构造那么必然有 \(u_0\to v,v\to u_t\),那么只有出现边为 $u_1\to v,u_2\to v\dots $ 的时候才能使得 \(u_0\to v\) 无法产生二类构造,同理会发现必须出现 $v\to u_{t-1},v\to v_{t-2}\dots $ 才能使得 \(v\to u_t\) 无法产生二类构造,那这两种边是相反的,必然存在中间一个点使得某个矛盾了,那么就必然存在这种 \(i\)。
如果对于每个点单独求,总的复杂度是 \(O(n^3)\) 的。大胆猜测直接求哈密顿回路即可,这样子每个点都可以作为起点。为了求回路,我们还是先求出哈密顿路径,然后再把它调整成环。
CF1514E Baby Ehab's Hyper Apartment
看到竞赛图联通性,肯定要去想 tarjan 缩点之后竞赛图是一条链这个关键结论。但是如何利用这个结论?
第一想法肯定是动态维护这条链,然后通过二分等手段定位其所有在的 SCC,往里面加点。
发现单点和 SCC 进行 check 二者关系是一件困难的事情,因为我们需要 check 双向联通性,但是询问一只能得到是否存在某条边的信息,这就很不利于我们的判定的,最坏情况下要遍历整个 SCC 才能知道二者关系,操作次数无法接受。
但是我们有操作二,可这仅仅能得到点 $\to $ 集合,无法得到集合 \(\to\) 点,不能进行双向串联。
所以第一个想法落空了,于是考虑先得到一个弱化版信息,我们可以充分利用得到边信息这个手段,这是竞赛图存在哈密顿路径,所以可以通过二分询问边信息来找到一条哈密顿路径。我们二分联通性,然后在当前序列中间插入点。另一种很好的做法是可以直接把题目给的操作一的交互接口看成 std::stable_sort 的 cmp 函数,对其进行排序,最后得到的序列也是某条哈密顿回路。次数 \(O(n\log n)\)。
找到这条路径之后,我们要把这条路径的若干个区间缩起来得到 SCC,缩 SCC 等价于找到这条链上的反向往回走的边。问题又来了,我们还是只能得到连边信息,不能直接得到联通性信息,所以不能询问相邻两个 SCC,而且是应该去询问一个前缀 SCC。
我们倒序枚举这条哈密顿路径上的点,通过操作二查询,如果 \(i\to \{1,2,3\dots i-1\}\),那么就可以把 \(i\) 所在 SCC 和 \(i-1\) 这个点缩在一起。次数 \(2n\)。
平面图
所有边要么不相交,要么只在端点处相交的图。
P4001 [ICPC-Beijing 2006] 狼抓兔子
平面图最小割 \(=\) 对偶图最短路
对偶图就是把平面的所有区域(单个区域就是边中间的空当),提取出来当作点,然后跨过原来的边连边。
注意边界上的边,新的点跨过它们要连接起点或者终点。
NFLS18401. 沙滩
给定坐标形式的平面图,保证其为一个 DAG,选出最多的点使得他们互不可达,并且最小化字典序。
考察一些平面图的观察,这里是平面图版本的 DAG 可达性刻画。
首先本题即选出若干点使得它们互不可达,DAG 可达性没啥性质是很难直接对于这个结构贪心或者 DP 的。我们可以按照 \(x\) 坐标对于这个图进行一些分层,一些点它们连出去的边必定在 \(y\) 坐标上构成一段区间,这些区间如果两两完全不交这个就可以按照 dfs 序建树,通过区间包含来得到可达性了,可是它们会在端点处相交。所以我们需要改一下刻画方式,对于每个点的所有出边按照斜率进行排序,正着遍历每条出边和倒着遍历每条出边,得到两个 \(dfs\) 序,\(u\) 能到达 \(v\) 当且仅当 \(dfn1_u<dfn1_v\) 且 \(dfn2_u<dfn2_v\)。
有了这个二维偏序的形式,我们可以直接对着第一个 dfs 序进行扫描线,对于第二个 dfs 序用树状数组维护,要求依次选出点的 \(dfn_2\) 单调递减,用树状数组优化即可。
关于字典序最小,我们直接对于每个点保存其 dp 值对应的最优序列即可,但是直接存显然会爆,由于这里有一个继承关系,所以可以用主席树维护,不需要存哈希值,由于每一个点只会插入一次,所以两个节点如果编号不同,就代表旗下的节点种类不同,每次比较的时候就是主席树二分。
时间复杂度 \(O(n\log n)\)。

浙公网安备 33010602011771号