图论题目合集
菜肴制作
要求把 \(1\sim n\) 排序,满足给定的所有条件,满足条件之后,编号越小要越靠前。(满足条件情况下,先让 1 最靠左,然后让 2 ……)
每个条件会给出两个数 \(a,b\),表示 \(a\) 必须在 \(b\) 之前。
解:
看起来很像拓扑排序。于是我们对于每个条件 \(a,b\),从 \(a\) 向 \(b\) 连一条边。每一个点只有在入度为 0 时,才能被选。
但是我们发现,如果我们这么做,无法保证编号小的更靠前:因为如果同一时刻有多个入度为 0 的点,我们并不知道选哪一个可以让编号小的更靠前。
因此转变思路,在反图上面作拓扑排序,每次如果有多个入度 0 的点,一定选其中编号最大的。
这样得出来的序列,就是所求序列的翻转。
P1129 矩阵游戏题解
校园网
第一问:强连通分量缩点后,入度为 \(0\) 的点的个数。
第二问:缩点后,\(\max(\)入度 \(0\) 个数,出度 \(0\) 个数\()\)。
抢掠计划
强连通分量缩点后,剩下一个 DAG。之后就很普通了。
魔法树
树剖
水管
最大流
CF877E
dfn 序转换到线段树上。
求和
见 \(k\le 50\),大喜,遂打表,以树上差分解之。
挖井
挖井可以看作和 \(0\) 连边。跑最小生成树。
推箱子
一种搜索的想法是一个状态里包含四个数:\(bx,by,mx,my\) 为箱子和人的位置。
但这个状态是 \(O((nm)^2)\) 的,需要优化。
我们发现只关心箱子的步数,那能不能只存箱子的位置呢?这显然不行,因为我们要知道人往哪边推。
于是两种方案折中一下:当人推箱子的时候,人一定在箱子相邻的四个位置之一。而且我们不关心人走到另一边花多少步。
新的状态:\(x,y,dir\) 表示箱子位置 \((x,y)\) 且人在箱子的 \(dir\) 方向。(上下左右)
每次转移状态,枚举箱子下一次移动是朝哪边,同时再用一次 BFS 看一下现在人的位置能不能移动到枚举到的推箱子的位置,注意这个过程中不能经过箱子的位置。
时间复杂度是 \(O(n^2m^2)\) 的,数据比较水,卡一卡就能过。但是我们可以优化。
考虑时间复杂度的瓶颈其实在于每次看人能不能移动到另一个位置,这也需要 \(O(nm)\) 的复杂度。
我们把每一个格子抽象为一个结点,相邻的合法格子之间连边。问题就变成了 \(x,y\) 能不能在不经过 \(z\) 的情况下相互到达。(不经过 \(z\) 是因为不能经过箱子的位置)
可以通过分类讨论 \(x,y,z\) 的位置判断。可以参考这里。
冗余路径
题意:给图增加一些边,使得图内没有割边。
答案:割边个数 \(\div \,2\) 上取整。
证明:对图进行边双连通分量缩点,得到一颗树。设这棵树有 \(x\) 个点的度为 \(1\),显然至少需要增加 \(x\div2\) 上取整这么多条边。
而发现如果把这些度为 \(1\) 的点都连通了,整个图也就没有割边了。
Edges in MST
Kruskal 分阶段进行,如果一条边想加入的时候,发现比他小的边构成的连通块,已经使他的两端连通了,说明这条边一定不选。
把剩下可能选的边构成一张图,这张图里面的割边就是必选的边。
Fairy
删掉一条边,让原图变成二分图,求方案数。
先把图里面所有连通分量找出来,如果这些连通分量中有至少两个不是二分图,方案数为 \(0\)。
如果全部都是二分图,方案数为 \(m\)。
剩下的情况我们就在那个不是二分图的连通分量里删边了。
考虑二分图的判定条件,一般就两种:可以二染色,没有奇环。
但是如果想判定二染色,需要 DFS 一遍,看起来不太行。考虑判断删掉一条边之后,要破坏所有奇环。
用一个图论经常使用的 trick:取 DFS 树。
因为是无向图,所以非树边只会是回边。
如果删一条回边使得奇环没了,显然必须要求图中只有一个奇环。剩下讨论删一条树边的情况。
下面定义:一条回边加若干树边形成的奇环称为错环,一条回边加若干树边形成的偶环称为对环。
因为要破坏所有错环,所以有一个 simple 的想法:取所有错环的树边的交集。这个交集的大小就是答案。
但是这个想法是错的。比如: 1-2-3-4
且 1-3, 1-4
。上面的方法会输出 2
。
观察这个算法为什么会错误:如果一个错环 + 一个对环,就会诞生一个奇环。
猜结论:如果一条树边不在任何一个对环,且在所有错环里,就可以。
(注意:我们这里不直接说奇环,而是分成对错环的原因是,对错环一定只有一条回边,但是奇环可能横跨多条回边。)
证明:这个命题其实有两个方向。
先证明:如果一条树边在所有错环,且在至少一个对环里,删了这条边不能让图变成二分图。
考虑一个错环 \(c_1\) 和一个对环 \(c_2\) 的异或。(图的异或:如果一条边恰好在一个图里存在,就保留;否则不保留。)
记这个异或为 \(c\)。显然 \(c\) 的边数是奇数,因为 \(c_1,c_2\) 的边数一奇一偶。
而考虑两个环的异或,发现这个异或一定可以把所有边划分成若干个环。
所以一定有一个奇环,所以一定不能让图变成二分图。
再证明:如果一条树边在所有错环,且不在任何对环里,删了这条边就行。
把这个图二染色。不管是否二分图。
记这条树边是 \(e\),如果不看回边,这条树边会把所有点分成两个连通块。
我们把其中一个连通块所有颜色取反,就构造出一种二染色的方法。(取反的 trick 也经常用)
证毕。
有了这个结论,我们只需要判断一条边是否在所有错环且不在任何对环就行了。(时刻注意错环的奇环的定义差别)
可以对每一条非树边用树上差分。但是如果求 LCA 会带一个 \(O(\log n)\)。我们发现其实每一条非树边都是回边,所以这条非树边一定有一端是另一端的祖先。于是求 LCA 其实是 \(O(1)\) 的。
于是我们就做完这题了。
整理一下思路:
-
读入。
-
对每一个连通块二染色。
-
枚举每一条边,看一下是否有 \(\ge 2\) 个连通块不是二分图。如果有,答案为 \(0\);如果所有连通块都是二分图,删掉任意一条边都可以;否则选定这个不是二分图的连通块的根。
-
一次 DFS。对每条回边,判断一下是对边还是错边,在对应的差分数组上加减;对每条树边,记录一下到达这个儿子走的边的编号,同时在递归进入儿子之后,让当前结点的差分数组累加上儿子的。
注意这里直接累加儿子的差分数组是正确的,同时可以大幅简化代码。
注意为了保证每条边只会统计一次,在每条边上要记录一个 \(val\),在走过这条边的时候把这条边的反向的 \(val\) 改成 \(0\)。(和求割边的 \(val\) 一个意思)
-
枚举这个连通块内的每一条边,看一下它两端的差分数组是否满足在所有错环且不在任何对环内。把所有满足边的加入答案数组。
-
如果错边只有一条,把这条错边加入答案数组。
-
输出,注意输出的编号要从小到大排序。
物流运输
思路:\(dp[i]\) 表示前 \(i\) 天的最小花费。
\(dp[i]=\displaystyle\min_{j=1}^{i-1}\{dp[j]+cost(j+1,i)\times (i-j+1)+k\}\)
其中 \(cost(j+1,i)\) 是指第 \(j+1\sim i\) 天都能使用的最短路长度。
数据范围很小,预处理随便跑。跑完预处理随便 DP 就行了。
难点是想到 DP。
Network Attack
删除两条边,使得图不连通,求方案数。\(1\le n\le 2000,1\le m\le 10^5.\)
先把割边这种简单情况拎出来解决了。
① 选两条割边,有 \(C_{cut}^2\) 种方法,\(cut\) 是割边条数。
② 选一条割边 + 一条非割边,有 \(cut\times(n-cut)\) 种方法。
③ 选两条非割边。下面解决这种情况。
因为现在已经和割边没有关系了,所以可以把每条割边连接的两个点合并成一个点。此时图里面就没有割边了。
经典 trick:DFS 树。
显然两条回边没办法不连通。
① 一条回边 + 一条树边。可以用一个 \(tot\) 数组统计跨越一条边的回边有多少条。
② 两条树边。我们发现,这两条树边一定有一条是另一条的祖先!如果它们是左右结构,因为没有割边,所以图依然会连通。
这时候我们的问题就变成:找两条有祖先关系的树边,使得图不连通。
记更浅的那条边为 \(e\),更深的那条边为 \(e'\)。
进一步分析,我们发现:记跨越 \(e\) 的回边边集为 \(S_e\),跨越 \(e'\) 的回边边集为 \(S_{e'}\)。 \(e,e'\) 可以让图不连通的充要条件是 \(S_e=S_{e'}\)。
也就是说,所有跨越 \(e\) 的,都要跨越 \(e'\)。所有跨越 \(e'\) 的,都要跨越 \(e\)。
假设此时所有跨越 \(e\) 的已经都跨越 \(e'\) 了。那这个时候有 \(S_e\subseteq S_{e'}\),那我们只需要再来一个 \(|S_e|=|S_{e'}|\) 就行。而 \(|S_e|=|S_{e'}|\) 的判断可以用上面的 \(tot\) 数组完成。
现在的问题又变成了对于一个固定的 \(e\),怎么找到所有 \(e'\) 使得所有跨越 \(e\) 的边都跨越了 \(e'\)。因为 \(1\le n\le 2000\),所以树边条数 \(\le 2000\),我们可以直接枚举两条树边。
这时我们只需要判断跨越 \(e\) 的边是否跨越了 \(e'\) 即可。有一个想法就是对每一个点记录一个属性 \(high\):从这个点的子树里走,要走到这个点或者这个点上方,最深能走到多深。(一开始就在这个点不算)
如果我们求出了这个 \(high\),要判断是否跨越,直接看一下 \(e\) 更深一端的 \(high\) 和 \(e'\) 更深一端谁更深就行了。
那怎么求 \(high\) 呢?有一种错误的想法是和求 \(low\) 类似求,但是这是错的。原因是当结点往浅走,子孙的 \(high\) 可能不再合法,但是又找不到次浅的。
我们可以用并查集做。先把所有回边按照靠上的点的深度从深到浅排序。显然一条回边会把 这条回边两端路径上 所有还没被赋值的点的答案 赋值为 这条回边较靠上的点。
可以用并查集,类似 People are leaving,把所有相连的答案被赋值的点合并到一起。每次枚举回边路径上的点,可以利用并查集跳过连续的被赋值的点。
但是,如果用按秩合并好像会导致代表元素不是最浅的点?
没关系,可以用一个额外的数组记录每一个连通块内最浅的点是哪个。
Even and Odd(无题号)
给定一张连通无向图,两点之间的距离若为奇数,称为奇对;偶对类似定义。(距离:简单路径长度,若距离可奇可偶,啥也不算)
注:关于简单路径的问题,尝试先想没割点的,再想圆方树。
先考虑没有割点的情况(点-二连通)。
如果是二分图,很简单;而如果不是二分图,就说明有奇环,可以推出任意两个点之间的距离奇偶性都不确定。
如果有割点,考虑圆方树。(圆方树:把割点全部删除,剩下每一个连通块都是方点,每个割点都是圆点,每个圆点和对应的方点连边,得到一棵树)
观察到:如果一个方点内部不是点-二连通的,发现如果两个点在这个方点的两侧,这两个点的距离奇偶性也是不确定的。
所以每一个不是二连通的方点,会把圆方树切成若干个连通块。对这些连通块内部各自求答案累加即可。
平衡的技能树(无题号)
给定一张图,修改某些边权,使得最小生成树和最大生成树费用相同。(其实就是每颗生成树费用相等)
先考虑简单情况:一个圈。
这种情况等价于要求环上每条边的边权相等。所以最优解就是找出出现次数最多的边权保持不动,其余的边都改成这个边权。
进一步:点二连通分量。
还是要猜所有生成树费用相等的等价条件。
显然每条边费用相等是一个充分的条件。那这是不是必要的呢?感觉好像是,尝试证明!
先从图里面取出一个环,显然这个环上所有边边权应该相同。
再考虑一个“耳朵”:从这个环上面长出去的一条路径。
发现无论是连着耳朵上两个点的边,或者连着原本的环上一个点还有耳朵上一个点的边,都能找到一个环。所以所有边边权相等。
于是证明了点二连通的情况。下面考虑圆方树。
每个方点内部边权相等。而如果一条边有圆点(割点),这条边显然不管在任何生成树里面都会选。
所以只需要把每个方点内部的边权都改成这个方点内部边权的众数即可。
铁人两项
给定无向图,计数三元组 \((s,c,f)\):存在简单路径 \(s-\dots-c-\dots-f\)。
简单情况:点二连通。
任意三个都有解!(注意有顺序,是 \(A\) 不是 \(C\))
证明的话,也是先从一个环说起,然后不停加耳朵,画画找路径。
进一步:圆方树。
分类讨论 \(s,c,f\) 所在方点/圆点,是否同节点等。讨论 \(c\) 是否是割点,可以用树形 DP 统计。
Tourists
一个非常显然的事实就是:当你到达一个点双时,一定存在一条简单路径,从外部进入这个点双,然后经过点双里面的权值最小点,然后再走出这个点双。所以一个点双对答案的贡献必然是点双里面的最小权值
于是我们可以建立圆方树,方点的权值为点双中的最小圆点权值。然后原图就变成了一棵树,询问时就可以直接树剖套线段树求路径最小值了
但是修改操作似乎并不是非常方便。因为一个圆点的权值变动,可能会引发与之相连的方点权值变动(当这个圆点是点双中的的最小权值点时会发生这件事情)。那么我们可以对每个方点维护一个multiset,里面存所有与之相邻的圆点权值,然后权值就是multiset中的最小值,每次修改就删掉原来的权值,插入新的权值。然后我们每修改一个圆点的权值,我们就遍历与之相邻的所有方点并按上述方法修改multiset
但这样是会被菊花图卡成n方的。因为菊花图的根节点,有n-1个方点与之相邻,每次修改都遍历一遍的话,就GG了
于是一位巨佬告诉了我更加优秀的方法:对于一个方点,multiset里面存它所有子节点的权值。然后修改一个圆点时,就只需要动它父亲的multiset(它的父亲必然是一个方点)。询问时,仍然可以正常询问,只不过如果lca是一个方点,那还要额外计算fa[lca]的权值对答案的贡献
具体实现需要用Tarjan求一下点双,然后还要一棵维护区间最小值的线段树,以及STL的multiset
Messengers
无向图,有两个人初始都在一号节点,你可以给两人分别设定一条路径,他们就会按照这个路径走。
有一个结点(不是 \(1\))是陷阱结点,只要访问到这里,人就会停下来。
求一种安排顺序,使得无论如何,每个结点都被访问过。(访问就是到达结点)
结论:如果 A 的路径是 \(A_1\sim A_n,(A_1=1)\),B 的路径是 \(B_1\sim B_n,(B_1=1)\)。则如果是可行的,必须满足 \(A_2\sim A_n\) 是 \(B_2\sim B_n\) 的 reverse。
证明:如果 \(B_n\) 不是 \(A_2\),则我们在 \(A_2\) 设为陷阱,那 \(B_n\) 就访问不到。
那我们只需要满足,路径任意一个前缀都是点双连通的。
而这是一个等价条件:如果存在不是 \(1\) 的割点,就无解!
所以我们判断无解,只需要判断是否存在不是 \(1\) 的割点即可。然后我们就能从 \(1\) 出发,依次访问每一个分支。两个人的访问顺序相反就行。
Museums Tour
看到周期,考虑拆点
把每一个博物馆 \(i\) 在第 \(j\) 天到达抽象为一个点 \((i,j)\)。如果第 \(j\) 天的博物馆 \(i\) 能到达第 \(k\) 天的博物馆 \(l\),就从 \((i,j)\) 向 \((k,l)\) 连线。
显然一个强连通分量里的都可以取到,我们强连通缩点,一个点的权值定为这个点内部可以到多少个不同的博物馆。
然后就可以 DAG 最长路了。
但是有个问题:会不会 \((i,j)\) 和 \((i,j')\) 在不同的SCC里,导致重复计算?
不会,因为如果 \((i,j)->(i,j')\),显然 \((i,j)->(i,j')->(i,j+d(j'-j))->(i,j)\),所以 \((i,j')->(i,j)\),所以 \((i,j),(i,j')\) 应该在同一个SCC内。
这题卡 vector,要用前向星;还卡栈空间,加了个 inline 就过了。
Paths and Trees
题意:找到边权和最小的最短路树。
普通最短路树:用 Dijkstra,每次取出来一个点进行松弛的时候,如果点 \(A\) 成功松弛点 \(B\),记录 \(pre[B]=A\)。结束后 \(pre[x]\) 就是 \(x\) 的父节点。
边权最小最短路树:
-
成功松弛的情况有两种,一种是到达新的点的距离严格小于原本的,这种肯定必须更换 \(pre[]\);另一种新距离相等,于是我们可以比较一下这一次的边权和原本的边权,保留更小的。
-
我们发现上面的比较边权其实没必要。因为最短路长度相等,\(pre[x]\) 比 \(now\) 更先被拿出来松弛 \(x\),肯定是因为 \(1\rightarrow pre[x]<1\rightarrow now\),所以 \(pre[x]\rightarrow x>now\rightarrow x\)。因此,如果最短距离相等,也可以直接替换。
毛毛虫
一张图被称为毛毛虫,当且仅当有一条路径,使得每个点到路径上的距离 \(\le 1\)。一次操作可以合并两个相邻的结点,连接他们俩的边变成自环。(毛毛虫不能存在重边,但是可以自环)
先考虑环:显然要合并 \(n-1\) 次。
然后考虑边双:也需要合并 \(n-1\) 次。
于是边双缩点成树。显然找一条直径作为最终毛毛虫的路径。
圆桌骑士
求有多少个点不在任何一个奇环内。
关键结论:每个点双,要么全不在,要么全在。
卧底行动
有多少种方法把图分成一个独立集和一个团?(之间的边随意,不能全部是独立集或者全部是团)\(n\le 5000\)。
一个点,要么属于独立集,要么属于团。于是考虑用 2-sat 求解。
用 2-sat 求出一组解之后,我们还要求出解的个数。我们发现,新的解要么是从原解的一边,拿出一个点放到另一边去;要么是从原解的两边交换一对点。
如果是拿出一个点放到另一边:
-
团放到独立集。我们要求出团里面有多少个与独立集的不连边,同时注意要保证团的点个数 \(\ge 1\)。
-
独立集放到团。和上面同理。
如果是交换:
对于换到团的那个点:要么连接团里面所有点,要么只不连接和他交换的那个点。
对于换到独立集的那个点:要么不连接独立集里任何一个点,要么只连接和他交换的点。
枚举两个点判断即可。
排列最小割
(高五 Class 3)
题意:给定图,找出一个点的排列 \(v_1\sim v_n\) 使得 \(\sum \mathrm{mincut}(v_i,v_{i+1})\) 最大。
首先求出最小割树。(Gusfield 等价流树)定义两点之间的距离为它们树上路径边权的最小值。我们就是要找一个排列,使得 \(\sum dist(v_i,v_{i+1})\) 最大。
取出树上最小的边 \(e\)。我们要尽量少经过这条边。
观察:存在一个最优解 OPT,只经过 \(e\) 一次。若 \(a>b>c>d\) 且 \(a>b\) 和 \(c>d\) 都经过了 \(e\),可以调整法证明。
于是可以递归的得出答案就是最小割树中所有边之和。
Qtree 6
对每个结点维护:\(w[x]\),表示如果 \(x\) 是白色, 以 \(x\) 为根的子树中 \(x\) 所在的白色连通块大小。\(b[x]\) 类似定义。
(只考虑黑变白,白变黑对称)
假设 \(u\) 黑变白,我们找到 \(v\):\(v\) 是离 \(u\) 最近的祖先且 \(v\) 为白色。如果 \(u\) 所有祖先都是黑色,令 \(v\leftarrow rt\)。
然后我们让 \(fa[u]\sim v\) 的 \(b[]\) 都减去 \(b[u]\),\(w[]\) 都加上 \(w[u]\)。
怎么找最近的白点?可以线段树维护区间内第一个 0。
可以树剖维护,\(O(n\log^2 n)\)。
寻宝游戏
小蓝书“异象石”。
建造军营
很显然能看出是个边双缩点 + 树形 DP。
先缩点,统计每个大点内部有 \(V[i]\) 个点,\(E[i]\) 条边。并且统计每个点的子树内的边数 \(s[i]\)。
先考虑 \(dp[i]\) 表示 \(i\) 的子树内有多少种方法。但是这样不好转移:递归求完 \(dp[son]\) 之后,不同的子树之间会影响。
所以 \(dp[i][0]\) 表示 \(i\) 的子树内一个军营也没有的方案数,\(dp[i][1]\) 表示 \(i\) 的子树内至少有一个军营,且所有军营要通过看守的边与 \(i\) 连通的方案数。
显然 \(dp[i][0]=E[i]\times \prod(2\times dp[son][0])\)。这个 \(2\) 是通往子树的那条边。
\(dp[i][1]\) 怎么求?可以用类似树形背包,不停往 \(dp[i][1]\) 里增加子树的影响的方法求:
若当前要增加子节点 \(x\):(注意 \(dp[i][1]\) 要求必须选)
-
\(i\) 的子树还没有:\(x\) 子树内必须选,\(dp[i][1]\leftarrow dp[i][0]\times dp[x][1]\)。
-
\(i\) 的子树内选了:则 \(x\) 选不选都可以。\(dp[i][1]\leftarrow dp[i][1]\times (2\times dp[x][0]+dp[x][1])\)。
因此 \(dp[i][1]=dp[i][0]\times dp[x][1]+dp[i][1]\times (2\times dp[x][0]+dp[x][1]).\)
注意是赋值运算,而且 \(dp[i][1]\) 和 \(dp[i][0]\) 要同步更新,且 \(dp[i][1]\) 更新要在 \(dp[i][0]\) 之前。
Cheap Robot
有几个观察:
-
当机器人在 \(u\) 时,电量必须 \(\ge\) 到达最近充电桩的距离。
-
假设当前在 \(u\),从 \(u\) 走到最近的充电桩再走回来,点亮不会变差。
从每个充电桩跑最短路,设 \(d[x]\) 为 \(x\) 到最近的充电桩的距离。
因此我们只需要记录能到哪个点,只要能到,这个点的电量就可以变成 \(C-d[u]\),而且最高也是 \(C-d[u]\)。
考虑从 \(u\) 成功走到 \(v\) 需要满足什么条件:\(C-d[u]-W_{u,v}\ge d[v]\)。
转换一下,\(d[u]+d[v]+W_{u,v}\le C\)。于是我们令新边权 \(W'_{u,v}=d[u]+d[v]+W_{u,v}\),重新建立一张图 \(G'\)。
若 \(a\) 能走到 \(b\),充要条件就是 \(G'\) 上有一条路径 \(a\rightarrow b\) 上边权最大值 \(\le\) 初始电量。
因此我们就是要最大边权最小,可以对 \(G'\) 求 MST。
切树
长链剖分原理。
\(dp[u][x]\) 表示 \(u\) 的子树内切若干次,且 \(u\) 能到达的最深的结点深度为 \(x\) 的方案数。
以合并子节点影响的方式计算。当前在合并 \(v\)。
-
不割 \((u,v)\),\(dp[u][\max(p,q+1)]\leftarrow dp[u][p]\times dp[v][q],(p+q+1\le k)\).
-
割 \((u,v)\),\(dp[u][x]\leftarrow dp[u][x]\times dp[v][p],(p,x\le k)\).
注意转移之间会有影响,用临时数组记录。
暴力做是 \(O(nk^2)\) 的,但是长链剖分优化 \(O(nk)\)。
变向
可以二分答案 \(x\)。
对于边权大于 \(x\) 的,我们保留它们,然后 topo 判环。
在 topo 判环的过程中,可以求出每个结点的 topo 序。对于边权 \(\le x\) 的,如果是从 topo 序大的指向小的,就要改变。
ABC277H
自己点进去看题意。
巧妙转化:因为 \(nm\le 10^6\),可以用 \(m\) 个布尔型变量表示一个变量:\(v_{i,j}=true\) 表示 \(x_i\ge j\)。
于是这题就可以转化为 2-sat 问题。(需要注意 \(v_{i,0}\) 必须为 \(true\) 和 \(v_{i,m+1}\) 必须为 \(false\),这可以通过 \(v_{i,0}\) 向 \(v_{i,1}\) 连边这种方式来解决)
(NOI2017)游戏
如果 \(d=0\),裸的 2-sat。但是如果 \(d\neq 0\),\(O((n+m)\times 3^d)\) 跑不过去。
我们其实只需要枚举 x
的位置为 a
或者 b
,就能覆盖所有情况了。所以是 \(O((n+m)\times 2^d)\)。
Fair
题意:给定无权无向连通图,每个点有颜色。颜色种数 \(k\)。求出从每一点出发,要经过至少 \(s\) 种不同的颜色,至少经过多少条边。(\(n,m\le 10^5,s\le k\le 100\))
对每一种颜色建立一个超级源点。依次从 \(k\) 个超级源点 BFS,可以求出每个点到每一种颜色的最短路。对于每一个点,将最短路距离排序,取前 \(s\) 小即可。
Is it Flowers ?
题意:判断一张无向图是否是 k-花。k-花:中间一个长度 \(k\) 的简单环,环上每个点又带出一个长度 \(k\) 的环。
满足以下条件的,就是 k-花:
-
有 \(k^2\) 个结点和 \(k^2+k\) 条边。
-
每个节点的度只能是 \(2\) 或 \(4\)。
-
连通。
-
切断所有连接两个度为 \(4\) 的节点的边后,恰好剩下 \(k\) 个长度为 \(k\) 的环。
前三个很好判断,第四个条件只需要枚举所有边,暴力从 vector
里删除即可。
注:从 vector
里删除,可以先遍历所有元素,如果遇到要删除的,就交换到 back 上,然后 popback。
The Shortest Cycle
给定序列 \(a\),若 \(a_i\&a_j\neq 0\),则 \(i,j\) 连边。求这张图的最小环。
发现连边的概率很大,根据抽屉原理,只要 \(n>128\),必有三元环。
\(n\le 128\) 时跑 Floyd 即可。
采蘑菇
SCC 缩点 + DAG 最长路。
城市规划
给出 \(m\) 对关系 \((a_i,b_i)\),要求构建一个图,使得所有关系 \((x,y)\) 在图中满足 \(x\) 可达 \(y\)。问至少需要多少条边。
先按给出的关系构建有向图。分成不同连通块考虑。
-
当前连通块是 DAG,按照拓扑序形成一条链即可;
-
不是 DAG,排成一个环即可。
最大生成基环树
题目:求出一颗生成基环树,边权和最大。
可以在 Kruskal 过程中额外对每个连通块记录这个连通块是树还是基环树。
同样边权排序,但是这次边可以两头都连树,只是不能两头连基环树。
最小圈
01 分数规划。(其实不用理解得那么玄乎)
首先,越小越好,越大越可行,二分答案 \(mid\)。
接下来判断是否存在一个环的平均值 \(\le mid\)。我们只要给每条边的边权 \(c_i\leftarrow c_i-mid\),问题就变成判断是否存在负环。
从分数规划角度理解:环就是有些边选了,有些边没选;有些点选了,有些点没选。
令每个点的权值 \(b_u=1\),答案就是 \(\dfrac{\sum edge}{\sum ver}\)。
二分,\(\dfrac{\sum e}{\sum v}\le mid\iff \sum e-mid\times \sum v\le 0\),而 \(\sum v\) 就是环上点的个数。所以只要让每条环上的边贡献一个 \(-mid\) 即可。
前k大边
根据原图 \(G\) 定义一类改造图 \(G_x\):把 \(G\) 中所有边的边权 \(w\) 改成 \(\max(w-x,0)\)。
若答案路径的第 \(k\) 大的边边权是 \(w_k\),则 \(ans=G_{w_k}\) 上 \(1\rightarrow n\) 的最短路长度加 \(k\times w_k\)。
若 \(x\neq w_k\),则 \(G_{w_k}\) 上 \(1\rightarrow n\) 的最短路长度加 \(k\times w_k\) \(>ans\)。
所以枚举 \(w_k\),求 \(G_{w_k}\) 上最短路,然后把所有最短路长度加 \(k\times w_k\) 取 \(\min\) 就是 \(ans\)。
三点连边
一定是一条直径 + 离直径最远的点。
从直径上每个点依次出发 BFS 它不经过直径能到的点。
Jumping Station
把移动的上下左右改成左上、左下、右上、右下。则最终目的地是 \((A+B,A-B)\)。
(以前移动的方式是 \((\pm d_i,0),(0,\pm d_i)\)。现在每次移动的方式是 \((\pm d_i,\pm d_i)\))
则 \(x,y\) 两维可以分开考虑。
目标:从 \(d_1\sim d_n\) 中选若干个数和为 \(w=\frac{A+B-\sum d_i}{2}\)。
DP,\(f[i][j]\) 表示前 \(i\) 个数和为 \(j\) 是否可行。
但是这个复杂度是 \(O(n^2D)\) 的,\(i\) 的范围 \(n\),\(j\) 的范围 \(n\times D.\)(\(D\) 是 \(d_i\) 的上限)
如何优化?
-
bitset 优化。\(O(\dfrac{n^2d}{64})\)。
-
我们找到一个数 \(b\),\(X=d_1+\dots+d_{b-1}\le w,d_1+\dots+d_b>w\)。
定义 Balanced Set:\(\{1,2,\dots,b-1\}\) 是一个 BS。 所有能通过下列两个操作从 \(\{1,2,\dots,b-1\}\) 变化到达的也是 BS。
-
balanced insert:插入一个 \(\ge b\) 的数。这个操作要求当前的和 \(\le w\)。
-
balanced delete:删除一个 \(<b\) 的数。这个操作要求当前的和 \(>w\)。
观察:所有 BS 的总和 \(\in [w-D,w+D]\)。
(\(D\) 是 \(d_i\) 的上限)
观察:最优解也是一个 BS。
为了方便,重新设状态定义,从 \(b\) 处向两边延申。
\(f_{l,r,j}\) 表示考虑 \([l,r]\) 内的操作,是否可以凑出 \(j\)。
初值 \(f_{b+1,b,X}=true.\)
转移:
-
\(f_{l,r,x}\leftarrow f_{l,r-1,x}\;,\;f_{l+1,r,x}.\)(不选)
-
\(f_{l,r,x+d_r}\leftarrow f_{l,r-1,x}.\;(x\le w)\)
-
\(f_{l,r,x-d_l}\leftarrow f_{l+1,r,x}.\;(x>w)\)
但复杂度还是没变。再重新设计。
\(g_{r,w}\) 表示满足 \(f_{l,r,w}=true\) 的 \(l\) 最大是多少。若不存在则为 \(0\)。
-
\(g_{r,x}\leftarrow g_{r-1,x}.\)
-
\(g_{r,x+d_r}\leftarrow g_{r-1,x}.\;(x\le w)\)
-
\(g_{r,x-d_l}\leftarrow l.\;(x>w,l<g_{r,w})\)
从左边转移但不操作 \(l\) 的情况不用考虑,因为 \(g\) 是取 \(\max\)。
状态数少了,但转移复杂度没变。瓶颈在于第三类转移。
注意到 \(l<g_{r-1,x}\) 时,其会在 \(r-1\) 用第一类转移到。所以只要转移 \(l\ge g_{r-1,x}\) 即可。
对于一个 \(x\),转移复杂度为 \(O(\sum g_{r,x}-g_{r-1,w})=O(n)\),所以总复杂度 \(O(nD)\)。
-
Graph Cutting
易知仅当 \(m\%2=1\) 时无解。
对一颗树:自底向上,每个结点把所有没配好对的儿子两两配。
如果没配对的儿子有奇数个,则 多余的儿子 - 自己 - 父结点 配成一条。
对于图:找 DFS tree,对于回边 \((u,k)\),\(k\) 是 \(u\) 的祖先。那我们只需要在处理 \(k\) 的时候把 \(u\) 也当作 \(k\) 的儿子即参与配对即可。
Pairs of Pairs
找出 DFS tree,判断层数是否直接构成一条路径。
否则,深搜树每一层的结点两两配对,每层最多一个结点没用。
单行道
边双缩点,显然一个边双内的边都不确定。
把要向上走视作 +1
,向下走视作 -1
。对于一个限制 \((x_i,y_i)\),找到它们所在的边双 \(X_i,Y_i\),然后让 \(X_i\rightarrow rt\) 的割边权值都 \(+1\),\(rt\rightarrow Y_i\) 的权值都 \(-1\),这可以用树上差分。
差分完看看每条边的权值是正是负即可。
异或替换
数组最后加上一个数等于所有数的异或,每次操作就相当于交换一个数到最后一个位置。
判无解:如果有目标数初始没有,-1
;否则必有解。
对于每个 \(a_i\neq b_i\) 的位置,使 \(a_i\) 向 \(b_i\) 连边。
答案 = 边数 + 连通块数 - 1.
Company
一些点的 LCA = dfn 最大最小的结点的 LCA。
所以要改一定是删除 dfn 最值。
用一颗线段树维护。
炸弹
第一步:建图。
\(O(n^2)\) 肯定是不行的,但我们可以发现:只需要每个点两边最近的能引爆这个点的,向这个点连边即可。
(用单调队列做)这样边数到了 \(O(n)\)。
第二步:求解
SCC 缩点。一个很直接的想法是:每个点引爆的个数,就是它所在 SCC 的后继个数。
但是这是不行的!如果直接找后继,\(O(n^2)\);而 DP 也不行,因为有后效性了,前面的点不止引爆一个直接后继,还会连着爆一大堆。
那怎么办?
简单的观察:每个炸弹最终引爆的所有炸弹,一定是一个区间。
这样就可以 DP 了,从每个 SCC 缩点做 DP,求出每个 SCC 引爆的左端点和右端点。
SGU268: n-SAP
定义 P: 长度 \(n\) 的一个排列;AP:删掉某个数后,构成一个 P;SAP:任意长为 \(n+1\) 的子串都是 AP。
给定两个 P \(x,y\),求一个最短的 SAP,包含 \(x,y\) 为子串。
记这个 SAP 为 \(a\),则 \(a\) 中每个长为 \(n\) 的子串只有两个情况:① 是一个 P ② 某个数出现 \(2\) 次,某个数出现 \(0\) 次,其余都出现 \(1\) 次(我们把这个情况称为 类P)。
可以尝试构图 \(G_0\)。对于所有长度为 \(n\) 的满足条件 ①/② 的序列,抽象为一个结点。如果某个序列可以转移(末尾添加一个数且后 \(n\) 个数还满足 ①/②)到某个序列,则前者向后者连边。
建完图,求 \(x\rightarrow y,y\rightarrow x\) 最短路就可以求出答案。
但是这样太慢了,点边太多了,点就至少是阶乘级别的。
观察:对于类 P,在图中的前驱、后继都只有一个。(缺哪个添哪个)对于 P,后继不为 \(1\) 个。
那么在 \(G_0\) 中,所有类 P 的点都是入度出度为 \(1\),可以把这个点缩掉。
\(G\):把所有类 \(P\) 的点缩掉,图里面只保留 P,但是图里面结点的连边带权,表示省略了多少个类 P 的转移过程。
观察:\(p_1p_2\cdots p_n\) 会向 \(p_{i+1}\sim p_n p_1\sim p_i\) 连边,边权是 \(i\)。
但这还是没法帮忙优化到通过。接下来是重头戏。
考虑把每个 \(n\) 子串看作一个环,同时加上一个起始字符 \(S\),规定每个环代表的序列是从 \(S\) 开始顺时针读一圈得到的序列。每个子串变动的操作就可以看作是 \(S\) 和某个字符交换了位置。
观察:\(S\) 与某数交换位置的代价(边权),是 \(S\) 顺时针走到此数位置的步数。
我们现在把每一个环的状态看作一个结点。则原来的终点现在对应很多个环(这些环本质相同,但是旋转了)。答案变成了到这些环的最短路取 \(\min\)。接下来怎么做?
研究对于固定的一个起点一个终点怎么求。
设起点 \(i\) 的位置是 \(old_i\),终点 \(i\) 的位置是 \(new_i\)(包括计算 \(S\) 的)。则这里我们再把环上每个位置抽象为结点,\(old_i\) 向 \(new_i\) 连边,边权是 \(old_i\) 逆时针走到 \(new_i\) 的步数。
我们要移动次数最少,也就是 \(S\) 顺时针最少,也就是所有非 \(S\) 的逆时针最少。
显然有一个下界:\(\displaystyle\sum_{i=1}^ndist(old_i\rihgtarrow new_i)\),\(dist(old_i\rightarrow new_i)\) 表示在环上 \(old_i\) 逆时针走到 \(new_i\) 的步数。
尝试构造一个方案达到下界。
这张图所有点出入度都为 \(1\)。观察:每个区域被 \(old_i\rightarrow new_i\) 的边穿过的次数都相等。
我们只需要让 \(S\) 的移动恰好穿过每个区域这么多次,就能达成目的,而这显然是可行的。
Dining
建图分三个部分:左边食物,中间奶牛,右边饮料。
\(s\) 向每个食物连容量 \(1\) 的,每个饮料向 \(t\) 连容量 \(1\) 的。(表示每个食物饮料只能使用一次)
每只奶牛拆成入点出点。每个奶牛喜欢的食物向这只奶牛的入点连边,入点向出点连 \(1\) 容量边,出点向喜欢的饮料连边。
Steady Cow Assignment
二分这个差值,然后枚举最小的不满意度是多少,可以得出每只牛要位于它心中 \([i,i+dif]\) 的牛棚。然后对于枚举的每种情况都跑 Dinic 检查是否可行。
建图:\(s\) 向每只牛连容量 \(1\) 的边,牛棚向 \(t\) 连容量为牛棚容量的边,牛向心中 \([i,i+dif]\) 的牛棚连边。
电影迷
\(s\) 连正权,负权连 \(t\),依赖关系直接连,求最小割。
数字梯形问题
问题一:入点出点。
问题二:连出入点都不用了,要注意第 \(n\) 行连 \(t\) 的边容量也要是 \(inf\)。
问题三:把问题二中所有连边容量改成 \(inf\)。(除了 \(s\) 向第一行的连边还是容量 \(1\))
修车
注意一个事情:并不是所有车的修理时间都会叠加,只有同一个师傅修的车会叠加等待时间。
平均用时最小 = 总用时最小。
一般的思路是 总用时 = 第一辆车等待用时 + 第二辆车等待用时 + ...
这么做是做不出来的,应该用 总用时 = 第一辆车修理用时*n + 第二辆车修理用时*(n-1) + ...
把每辆车抽象为一个结点,(第 \(i\) 个工人所修理的倒数第 \(j\) 辆车)抽象为一个结点。
\(s\) 向每辆车连容量 \(1\) 费用 \(0\) 的边,表示每辆车只会修一次。
每辆车 \(c\) 向所有 “第 \(i\) 个工人所修理的倒数第 \(j\) 辆车” 连边,容量 \(1\) 费用 \(j\times T_{c,i}\)。表示如果 \(c\) 是第 \(i\) 个工人所修理的倒数第 \(j\) 辆车,它会贡献 \(j\times T_{c,i}\) 的总等待时间。
所有 “第 \(i\) 个工人所修理的倒数第 \(j\) 辆车” 向 \(t\) 连边,容量 \(1\) 费用 \(0\)。
跑最小费用最大流,最小费用就是最小总等待时间。
美食节
是修车的加强版。但是建边的思路还是和上题一样。
直接把图全建了,复杂度过高。这时候做一个优化:因为每一个厨师所代表的一系列点,一定是先用了倒数第一,再用了倒数第二 ……
所以我们没必要一开始就把每个初始对应的所有做菜位置的点全建了,初始每个厨师只放一个倒数第一的点,这个点被流了,再加倒数第二的点 …… 这样就行了。
旅行 加强版
翻译:遍历给定基环树(如果是树很简单)。遍历规则:可以从后继中选一个未访问的前进,或者沿第一次访问该结点的边回溯。求最小字典序的 DFS 顺序。
即可以去掉一条环边,然后求最小 DFS 序。注意字典序是有小的编号一定会走。
肯定从 \(1\) 出发。把所有点的邻点按编号从小到大排序,一定会按照这个顺序遍历。
放弃环边需要的条件:
-
以前没放弃过环边。
-
环边指向的点是这个点遍历的最后一个点。否则如果直接回溯,后面就回不来这个点了。
-
从此处回溯之后下一个到达的点比环边指向的点小。
综上,先把所有点邻点排序,再基环树找环,最后遍历。
关键代码细节挺多的。
bool f[500005] = {}, flag = false; //f是访问标记,flg=true表示放弃过环边
//当前点x,父节点pr,若当前点回溯,下一个访问会nx
void srh(int x, int pr, int nx) {
f[x] = true;
cout << x << " ";
for (int i = 0; i < e[x].size(); i++)
if (!f[e[x][i]]) {
// x->e[x][i]是环边且 以前没放弃过
if (cir[x] && cir[e[x][i]] && !flag) {
//若e[x][i]不是最后一个邻点,则不可以放弃,必须前进
if (i < e[x].size() - 1 - (pr > e[x][i])) //注意父节点的影响
srh(e[x][i], x, e[x][i + 1] == pr ? /*注意这里父节点不要被算!*/e[x][i + 2] : e[x][i + 1]); //修改回溯访问的结点
//如果放弃了反而更差,就不放弃
else if (nx > e[x][i])
srh(e[x][i], x, nx);
//否则从这里直接回溯
else
flag = true;
}
else
srh(e[x][i], x, nx);
/*
这里也可以写作 srh(e[x][i], x, inf)
因为进入这里说明不是环边,不是环边也就不允许放弃(只能自然回溯),也就不会用到nx
*/
}
}
Island
翻译:求出基环森林中,每颗基环树的直径之和。(带权)
考虑一颗基环树怎么求直径。
先用深搜树找环,记那个环在深搜树中深度最浅/深的结点为 \(up/dw\),那条回边为 \(bk\),深搜树中结点 \(x\) 的带权深度为 \(lvl[x]\),一个环上的点 \(x\) 在深搜树中在环上的儿子为 \(cs[x]\)。
直径分为两类:一类过 \(bk\),一类不过 \(bk\)。不过 \(bk\) 的一类就是树的直径,很简单。
对于过 \(bk\),必然可以看作 \(bk\) + \(up,dw\) 拓展出去的两条链 的形式。假设 \(up\) 在环上走到结点 \(x\) 然后离开了环,\(dw\) 在 \(y\) 离开了环。
\(x\) 和 \(y\) 之后的命运就已经是树的直径了,在上面已经处理过。唯一不同的就是统计环上的边。\(up\rightarrow x\) 的长度是 \(lvl[x]-lvl[up]\),\(dw\rightarrow y\) 的长度是 \(lvl[dw]-lvl[y]\)。
记 \(dp1[x]=lvl[x]-lvl[up]+\text{x 脱离环后的最长路径},dp2[x]=lvl[dw]-lvl[x]+\text{x 脱离环后的最长路径}\)。同时类似前缀和优化,因为环在深搜树上可以看作一条链 + \(bk\),所以额外算一个 \(mx[x]\) 表示 \(\max(dp2[x],dp2[cs[x]],dp2[cs[cs[x]]],\dots,dp2[dw])\)。
于是可以枚举 \(x\) 的位置,用 \(bk+dp1[x]+mx[cs[x]]\) 快速求出 \(up\) 从 \(x\) 离开环的最大路径长度。
但是还有一种情况没考虑:若 \(x=up\),即 \(up\) 直接从原地离开环,并且往祖先方向走。我们的 \(dp1,dp2,mx\) 都是以 "子树" 来描述的,不包括往祖先走的情况。(虽然不考虑这种情况疑似还有 80)
法一:用类似换根 DP 的方法,单独处理出 \(up\) 向上的长度。但是这违背了我们的初衷:还是把环上的点特殊化了,而且如果要拓展到仙人掌,就要对每个环上最浅的点都做一遍,很麻烦。虽然也是正确的,但是这里不予采用。
法二:仍沿用树形 DP 的思路。\(up\) 向上走的路径,负责结点不是 \(up\),而是这条路径上最浅的结点(LCA)。\(up\) 只需要向上汇报在 \(up\) 的 “子树”(包括 \(bk\))中的向下最长路径即可。
那么 \(up\) 向下的最长路径是多长呢?如果不过 \(bk\),就是 \(dp1[up]\);否则,就是 \(bk+\max(dp2[cs[up]],dp2[cs[cs[up]]],\dots,dp2[dw])=bk+mx[cs[up]]\),注意这里不包括 \(dp2[up]\),否则从 \(dw\) 上到 \(up\) 路径会和 \(bk\) 组成一个环,不再是简单路径。这样就求出了 \(up\) 子树的答案,并且对于更多的简单环,这个方法都同样适用。
注:这一题会有重边,但是重边其实只会用重边中边权最大的。
注2:求不过 \(bk\) 和过 \(bk\) 的,放一个函数就行了。
航班安排
把每个请求抽象为一个点,“每个请求只有一次” 启发我们把每个请求拆成入点出点。
之后就比较简单了,源点 \(S\) 汇点 \(T\)。为了限制总流量(飞机数量),建立新点 \(x\),\(S\) 向 \(x\) 连边,容量 \(k\) 费用 \(0\)。这里的 \(x\) 作用仅是一个限流器。
若对于某个请求,机场若能在 \(s\) 时刻前到达 \(a\) 机场,则 \(x\) 向该请求的入点连容量 \(1\) 费用 \(-f[0][a]\) 的边;若 \(t\) 时刻的 \(b\) 机场能在 \(T\) 之前回到机场,则该请求的出点向 \(T\) 连容量 \(1\) 费用 \(f[b][0]\) 的边。
请求之间也连边,若某个请求结束时的飞机可以赶到另一个请求的开始,则这个请求的出点向那个请求的入点连边,容量 \(1\) 费用就是从这个请求结束飞到另一个请求开始的费用。
单个请求的入点向出点连边,容量 \(1\) 费用 \(-c\)。
跑最大费用流。
剪刀石头布
考虑用 \(n(n-1)(n-2)/6\) 减去反面,发现每一个不是环的三元子图,必然唯一对应了一个入度为 \(2\) 的点。
进而我们发现对于一个点若入度为 \(d[x]\),则它可以选出 \(d[x](d[x]-1)/2\) 对入边,每一对入边都对应了一个不是环的三元子图。此时若 \(d[x]+1\),则对应的个数 \(+d[x]\),再加一,对应个数又加 \(d[x]+1\)。
我们要“不是环的三元子图”尽量少。
用已经定向的边统计每个点的初始入度 \(d[x]\),然后 ans -= d[x]*(d[x] - 1)/2
。
接下来建图。
先对每一个点 \(x\) 建一个点,就编号 \(x\)。\(x\) 向 \(t\) 连 \((n-2)-d[x]+1\) 条边,容量全是 \(1\),费用依次为 \(d[x],d[x]+1,\dots,n-2\)。每有一条边流了,表示将某条边定向了,使 \(x\) 的入度 \(+1\),相应地也要把 \(x\) 对应的非环三元组个数增加。
这里一个技巧就是我们要数量最小,所以在网络流的时候一定会优先流费用小的边。
再对每一条没确定方向的边 \((x,y)\) 建点 \(x+yn\)。
\(s\) 向每个 \((x,y)\) 连容量 \(1\) 费用 \(0\) 的边,表示每一条边只能确定一个方向。每个 \((x,y)\) 向 \(x,y\) 各连一条边,容量 \(1\) 费用 \(0\),若流向 \(x\) 表示定向为 \(x\leftarrow y\),否则定向为 \(x\rightarrow y\)。
跑最小费用最大流,得到的结果就是 将边定向后,多出来的非环三元组的个数 最少是多少
,让 \(ans\) 减去即可。
航空路线问题
\(1\rightarrow n\rightarrow 1\) 其实可以看作找两条 \(1\rightarrow n\) 的路径,不相交。
建图:因为要求了城市只能经过一次,所以很自然想到入点出点的建模。除了起点终点的入点向出点连的边容量为 \(2\),其它入点到出点的边容量都是 \(1\)。然后城市之间的航线就正常出点连入点即可。
\(in_1\) 做源点,\(out_n\) 做汇点。若流量非 \(2\),判断是否存在 \(1\rightarrow n\rightarrow 1\) 的路径,如果这也没有,才说明无解。
对于输出方案,在残余网络上做两次搜索即可。
方格取数问题
将棋盘黑白间隔染色,\(s\rightarrow white\rightarrow black\rightarrow t\)。
Card Game
一个想法:先枚举等级,保留能用的卡。把所有和为质数的卡连边,然后就是求最大独立集。
怎么证明是二分图?
注意到质数除了 \(2\) 都是奇数,先特判一下,只保留能量最高的可以用的 \(1\) 卡。接下来质数就只能是一奇一偶了。按照奇偶来分,确实是二分图。
所以就是求二分图最大独立集,也就是 总能量 - 二分图最小点覆盖,而二分图最小点覆盖可以用网络流:最小点覆盖建图网络流,结点与源汇为权值,结点间无穷,跑最大流(最小割)
新生舞会
01分数规划 + 费用流判定。
爬山
显然是二分图。
如果点 \(u\) 在所有最大匹配中,则先手必胜;因为每次先手都可以沿着一条匹配边走到右部。后手不可能走到一个没有匹配的点,否则就形成了 \(u\) 开头的增广路,不符合点 \(u\) 在所有最大匹配中。
否则先手必败。考虑另一个不包含 \(u\) 的最大匹配。先手每次只可能移动到一个匹配点,否则形成了一条首尾都是未匹配点的增广路,与最大匹配矛盾。而后手每次都可以沿着匹配边回到左侧。
如何判断是否包含于所有最大匹配?先求一个最大匹配,如果 \(u\) 已经不在此匹配,显然不包含;否则令 \(u\) 的匹配点试图再找一条增广路,如果找到了,说明 \(u\) 不包含,否则包含。
DAG 最小路径点覆盖
这里只讲怎么求最小路径点覆盖。
考虑一张二分图,把原图中每个点拆成两个点 \(x_i,y_i\),对于原图的一条边 \((u,v)\),在二分图中为 \((x_u,y_v)\)。
结论:答案 = n - 此二分图的最大匹配。
证明:最终路径点覆盖的所有选中的边,一定在二分图中构成了一个匹配。这是因为最终的覆盖由很多链组成,不会出现一个点多条出边或多条入边的情况,所以对应到二分图中就是每个点不会匹配上两条边。
而观察发现,在路径覆盖中每条链的末端,对应在二分图中就是左部的一个未匹配点。
泥地
显然一个木板一定会顶到端点处。因此每一个泥巴格子只可能被两种木板覆盖:左右方向和上下方向。
处理出所有左右、上下方向的泥巴段,就算只有一个泥巴格子,也要分成两个泥巴段。把每个泥巴段抽象为节点。若两个泥巴段有重叠的泥巴格子,它们之间连边。
每个点代表一块木板,每一条边都代表一个泥巴格子。每个泥巴格子都要被木板覆盖,即每一条边都要被点覆盖。所以就是二分图最小点覆盖。
连续攻击游戏
把每个装备抽象为一个点,每个数字抽象为一个点。每个装备向它的两个属性连边。
然后跑匈牙利算法。从数字 \(1\) 从小到大找增广路,如果 \(i\) 找不到了则答案是 \(i-1\)。
这里还有一个小 trick: vis 每次 memset 太慢了,可以把 vis 改成 int 数组,同时记录一个当前轮数 id,在搜索的时候如果这个节点的 vis 不是 id,说明这一轮它还没动,其 vis 改成 id。
P4843 清理雪道题解
GDOI2024 考前模拟2 T2 题解
长脖子鹿放置
我们可以很轻易构造一个图:若两个格子能相互长脖子鹿攻击,则在它们之间连边。最终求这个图的最大独立集。
但是一般图最大独立集是 NP,所以我们想证明这个图是二分图。观察发现,如果把每一行黑白间隔染色,每种颜色只能攻击到另一种颜色。证毕。
P6185 题解
UVA1411 Ants 题解
CF1045C
每个点双内距离都是 \(1\),缩点即可。
CF510E
题意:\(n\) 只狐狸,有年龄 \(a_i\),要坐在一些圆桌边吃饭。要求每张桌子边至少 \(3\) 只狐狸,且相邻的狐狸年龄和为质数。找出一组解或判断无解。
如果两个数 \(a_i\) 加起来是质数,则连边。必然是奇数连偶数。(\(a_i\ge 2\))因此是二分图。
因为是圆桌,每只狐狸都恰好与两只狐狸相邻,也就是每只狐狸匹配上两只狐狸。于是网络流建图(记 \(2\mid a_i\) 的为 \(X\)):\(S\rightarrow X\) 容量 \(2\),\(X\rightarrow Y\)(和为质数)容量 \(1\),\(Y\rightarrow T\) 容量 \(2\)。判断是否满流即可。
ARC080G Prime Flip 及其题解
分组作业
很好的一道网络流。
容易想到把每个人作为一个点,和 S,T 相连,割前面表示不同意,割后面表示同意。
本题难点在于 “合作” 和 “同意” 的区别。
这里最重要的想法就是把每一个组单独成立一个点,用来表示是否合作。 然后用每个人的点向组的点连 \(+\infty\) 的边来表示不同意就不能合作。
Order
一道简单的网络流,把每个任务建点,每个机器建点。
机器向 T 连边,容量为买的代价;S 向任务连边,容量为任务收入;任务向所需机器连边,容量为租机器费用。
所有收入 - 最小割 就是答案。
Underfail 及其题解
Incorrect Flow 及其题解
Good Transportation:最大流转最小割
题意:小明升任了 CF 国的大总管,他管辖的 \(n\) 个城市,编号为 \(1..n\) 。每个城市生产了 \(p_i\) 个货物,限制最多可以卖掉 \(s_i\) 个货物。对于每两个城市 \(i, j\) ,如果 \(i < j\) ,则可以最多从 \(i\) 运送 \(c\) 个货物到 \(j\) 。注意不能反向运送,却可以在多个城市之间送来送去。现在小明想知道,经过运输后,最多能卖掉多少个货物。
最大流建模非常简单但是慢了。但是我们转最大流为求最小割,而求最小割可以用 DP 做。 \(dp[i][j]\) 表示点 \(1\sim i\) 中割了 \(j\) 个不与 \(S\) 相连的最小费用。
滚动数组压维可过。
Anti-Palindromize
题意:定义一个反回文串为反转后没有一个位置相同。给定一个 \(n\) 长度字符串 \(s\),保证 \(n\) 是偶数。每个位置有一个价值 \(a_i\)。
将 \(s\) 的字符重新排列构成一个反回文串 \(t\)。定义这个排列 \(P\) 的价值 \(v(P)=\sum_{i,s_i=t_i}a_i\)。求 \(\max(v(P))\)。
把 \(s\) 重新排列不好用流表述,不如看做把所有字符都取出来然后重新分配。统计 \(s\) 每种字符出现次数 \(cnt_c\)。
给每种字符建立一个结点,每个位置也建立结点。字符串重新排列看做字符流到位置上。
为了使两个对应位置不相等,也就是一种字符的结点不能同时给到两个位置流。新建一个结点 \(tmp\) 向这两个位置连边,字符只向这个结点连容量 \(1\) 的边,保证了这两个结点只会接受来自不同字符的流。
如果对应位置刚好是对应字符,在 \(tmp\) 对应的边加上费用。