生成树问题
生成树
一个无向连通图的生成树是指该图的一个连通无环子图。而最小生成树就是所有生成树中总边权最小的。
割点和割边
定义1:给定无向图,如果删除某个顶点v后,导致图中的连通分量增多,那么称这个点是割点。
定义2:给定无向图,如果删除某个边后,导致图中的连通分量增多,那么称这个边是割边。
割边的求法非常简单,利用tarjan算法进行缩点后得到一棵树,这棵树中的所有边都是割边。这也意味着割边最多存在V−1条。
割点的求法类似,先求出任意一株生成树。如果根下面挂了一颗以上子树,那么根顶点一定是割点。对于其余顶点v,如果v不能访问到比父亲顶点dfn序号更小的顶点,那么就意味着父顶点对于v是一个割点。
DFS生成树
一篇不错的博客。
对于一个连通图图,我们可以通过DFS求出其中一个生成树,这样的生成树我们称为DFS生成树。显然只有一部分边被加入到了生成树中,加入的边我们称为生成边,其余边称为备用边(back-edge)。
DFS生成树特性:
- 所有备用边都连接某个祖先和其后代。
- 每条备用边的加入都会构成一个简单环。
- 对于任意一条生成边(u,v),其中u是v的父亲,(u,v)是割边当且仅当不存在连接u的祖先和v的后代的后备边。
- 连通分量是强连通分量当且仅当其中不存在割边。
讨论几个DFS生成树的应用:
题目1:给定一副连通无向图,不存在自环和重边,要求将每条边转换为有向边且要求转换完成后,所有顶点对之间都存在至少一条路径。判断是否存在方案,如果存在,则输出任意一种可行方案。
由于转有向图后强连通,因此有向图中不存在割边,那么原来无向图也一定不存在割边,即原图必须是强连通的。这是必要条件。
先生成无向图的一个DFS生成树,之后我们将所有的从生成边的方向设置为祖先指向后代,而将所有备用边的方向设置为后代指向祖先。
下面我们保证上面的图是强连通的。很显然从根顶点r到其余顶点都存在路径,现在考虑任意顶点v,假设其父亲为u,且其父亲到根存在一条路径,那么由于(u,v)不是割边,因此存在一条跨越(u,v)的备用边(s,t),其中s是t的后代。我们可以沿着路径v..t,s..u..r从v抵达r。利用归纳法可以证明所有顶点都可以抵达根,因此有向图是强连通的。
题目2:给定一副无向图,不存在自环和重边,且保证每个顶点的度数大于等于k。现在要求找到任意一个长度大于等于k+1的简单环(无重复顶点)。
首先生成DFS树,考虑DFS树的任意叶子顶点,与其直接相邻的生成边的数目为1,因此至少还有k−1条后备边。由于每条后备边连接不同的祖先顶点(不是父亲顶点),因此有一个祖先顶点至少距离当前顶点k,于是乎加上这条后备边就构成了一个长度大于等于k+1的简单环。
题目3:给定一副n个顶点的无向图,不存在自环和重边。要求要么找出一个大小为⌈√n⌉的独立集,要么找到一个长度至少为⌈√n⌉的简单环。
我们可以利用贪心算法来找独立集,每次选择度数最小的顶点,选择它并删除所有与其关联的顶点以及它,这个过程直到剩余所有顶点度数都大于等于⌈√n⌉−1或者没有顶点了才停止。在这个过程,由于找到的度数最小的顶点度数小于⌈√n⌉−1,因此每次选择最多使得⌈√n⌉−1个顶点不可用。如果所有顶点都被消除了,那么我们至少选择了⌈√n⌉轮,即我们得到了一个满足条件的独立集。否则,剩余的顶点的度数都至少为⌈√n⌉−1,而这保证我们一定能找到一个长度至少为⌈√n⌉的简单环。
提供一道题目。
题目4:给定一副n个顶点m条边的简单无向连通图(无自环重边),保证每个顶点度数都至少为3。给定某个数k,现在要求找到找到一条长度为⌈nk⌉的简单路径(无重复顶点);或者找到k条长度至少为3,且长度不能被3整除的环,且每个环都至少有一个独属于自己的顶点。
很显然对于每个叶子顶点,其至少有两条备用边连向不同的祖先,而这会构成三个环,其中至少一个环不能被3整除。且每个环都包含这个叶子顶点,我们可以将叶子顶点作为独属的顶点。
如果有k个叶子顶点,那么我们就直接求出解了。否则我们记di为第i个叶子顶点的深度,且ci为叶子数,那么一定有∑ci=1di≥n,因为从每个叶子向上走可以覆盖所有的顶点。之后利用鸽巢原理可以直接得出至少有一个叶子i深度满足di≥⌈nc⌉≥⌈nk⌉。这样我们就找到了路径了。
最小生成树
prim和kruskal
最小生成树的求法不外乎两种,prim和kruskal。两个都是贪心算法,正确性基于最小生成树类似的性质。
随意取一个顶点u,记所有与u关联的最小权边为e=(u,v),其中u≠v。现在我们证明e一定属于一株最小生成树,用反证法。假设最小生成树T中不包含e。我们知道e是与u关联的最小边,因此可以将e加入到T中,这样就会出现一个包含点u的环。删除环上与u相连的另外一条边e′,得到了一株新的树T′,而T'的总权不可能大于(加入e删除了e′,e的权重小于等于e′),因此weight(T′)≤weight(T)。这样我们就得到了包含e的一株最小生成树。
由这个观察我们可以推出贪心的流程。
-
创建一个空集T。
- 任意取一个顶点u,找到与该顶点u相关联的最小权重边e=(u,v),满足u≠v,从点集V中删除u、v,并将e加入到空集T中,之后合并顶点u、v,将合并后的顶点加入到V中。
- 如果V中只剩一个顶点了,那么就结束,此时T就是最小生成树。否则回到步骤2。
上面这个流程就是prim算法。由于并不限制步骤2的u的选择,因此你可以总是选择顶点1以及包含顶点1的合并顶点,这样整个流程就完全等价于dijkstra算法了,用最小堆优化后得到的时间复杂度为O(min((V+E)log2V,V2))。
prim算法是通过点去找边,也可以通过边去找点,下面的算法就是如此:
- 创建一个空集T。
- 从边集E中弹出权重最小的边e=(u,v),如果在生成树T中u、v不处于相同连通块,就将e加入T中,否则忽略e。
- 如果E为空集,那么就结束,此时T就是最小生成树。否则回到步骤2。
上面这个流程就是kruskal算法,我们可以提前对E进行排序,并用路径压缩并查集维护连通块,这样时间复杂度为O(E+Elog2E)。
题目1:给定n个顶点m条边构成的无向连通图,第i个顶点的权值为w(i)。要求找到一组边,构成生成树,且要求∑vdeg(v)w(i)最大
我们发现加入一条边(u,v),会导致结果增大w(u)+w(v)。我们可以将w(u)+w(v)作为边(u,v)的权值,用最小生成树算法找到最大生成树即可。
borůvka算法
borůvka算法也可以用于求最小生成树。它的原理非常简单,假设任意不同边都有唯一权重(否则我们可以拼接边的id来获得唯一权重)。
设对于不同的顶点,记(i,Xi)为与i相关的最小权重边。记T为最小生成树,则对于任意i,(i,Xi)属于最小生成树。
原因非常简单。假设(i,Xi)不属于最小生成树T,则将(i,Xi)加入,则必定可以用(i,Xi)替换路径i和Xi的路径上的与i相关联的那条边。
因此borůvka的如下:
- 如果已经得到生成树,生成树则一定是最小生成树,结束流程。否则进入第2步。
- 对于任意连通块C,找到权重最小的边(u,v),其中u∈C,且v∉C。并将(u,v)加入到最小生成树(需要去重)。回到步骤1。
由于每次迭代都至少会减少一半的连通块,一次你迭代最多发生logV次。每次迭代实际上可以O(E)完成,因此总的时间复杂度为O(ElogV)。
题目1:给定一个N个顶点M条边的无向连通图,其中前K个顶点是特殊的。构造新的一个包含K个顶点的完全图,其中边(i,j)的权重为原图中从i到j的最短路。要求找到新图的一个最小生成树。其中1≤K≤N≤M≤105。
提供一道题目。
利用borůvka算法可以O(MlogM)的时间复杂度找到以内每个连通块(仅特殊顶点)到其它连通块(仅特殊顶点)的最短边。
因此总的时间复杂度为O(MlogMlogN)。
子图最小生成树
对于一个包含n个顶点m条边的无向图,现在需要处理q个请求,每个请求选中一些顶点,要求输出这些顶点的最小生成树包含哪些边。这里1≤n≤20,n−1≤m≤(n2),1≤q≤106。
我们可以直接用kruskal算法O(n2q)解决这个问题。但是实际上我们可以优化到O(nq+n2n)。这个技术是从这里学到的。
记k=√n。记X为删除编号最大的k个顶点后的子图,记Y为删除编号最小的k个顶点后的子图。我们用上面的暴力算法分别计算X和Y的所有子集的最小生成树,这个过程时间复杂度为O(2(n−k)22n−k)≈O(n2n),空间复杂度则为O(2n)。
现在考虑如何处理请求,对于某个查询的集合S,我们可以断言可能组成它的最小生成树的边仅来自S&X和S&Y的最小生成树,或者一个端点是最小的前k个顶点,一个端点是最大的后k个顶点。可以发现这时候只需要考虑最多2n条边,由于kruskal的算法时间复杂度是与边数目相关,因此每个请求处理的时间复杂度为O(n)。
题目1:对于一个包含n个顶点m条边的无向连通带权图,且给定k张优惠券,额度分别为d1,…,dk。可以用优惠券减少某条边的权重,如果在权重为w的边上使用第i张优惠券,可以将该条边的费用减少min(di,w)。一条道路最多使用一张优惠券,一张优惠券也最多只能使用一次。接下来处理q次请求,请求如下:
- 给定起点s和终点t,查询两点之间最小距离,要求返回从s到t,可以任意使用优惠券的前提下,最少的费用。
其中1≤n≤20,n−1≤m≤(n2),1≤q≤106。
问题来自: SRM795Div1 1000
我们可以发现无论优惠券怎么改变,图中的最小生成树是不变的。且两点的最小距离,实际上正好等于某个包含这两个点的子图的最小生成树。
用上面的技术我们可以O(n2n)找到所有子图的最小生成树,同时用贪心的方法,较大额度的用于费用较大的路径(注意我们取出最小生成树的时候已经是排过序的了),因此时间复杂度为O(n2n)预处理,之后我们可以对状态进行下推来找到所有点对的最短路径,时间复杂度为O(n2n)。剩下的请求都可以O(1)回答。
最小瓶颈路
一条路径中,权重最大的边称为路的瓶颈,而从起点到终点的所有路径中瓶颈最小的路径称为最小瓶颈路。
对于两个不同的顶点i,j,记fij(x)表示只保留图中权重不超过x的边的前提下,顶点i与j是否连通(连通为1,不连通为0)。很显然这样的函数fij一定是非严格递增函数。假设i、j之间的最小瓶颈路的瓶颈权重为t,那么t一定是fij最小的能令其返回1的入参。
因此我们可以先对边集按照权重从小到大进行排序,之后按序将边的两个端点所在连通块合并为一个连通块。这个过程持续直到i和j处于相同的连通块中。
这个流程实际上和kruskal是如同的,因此我们可以相信最小生成树中的任意两个端点i、j之间的唯一路径就是图上的最小瓶颈路。
求所有最小生成树的并集
给定一张无向连通图,求其所有最小生成树的并集。
有两种方法。
第一种方法,就是先任意构建一个最小生成树,显然最小生成树中的所有边都要被包含进去。考虑任意未被包含进最小生成树的边e=(a,b),其连通两个顶点,考虑最小生成树中从a到b的唯一路径上权重最大的边e′,由于最小生成树具有最小瓶颈路的性质,因此e′的权重不可能大于e。如果e′的权重恰好等于e,那么我们可以从最小生成树中删除边e′并加入e来获取一颗新的最小生成树。
上面的总的时间复杂度为O((n+m)log2n),路径操作可以用LCT实现。
第二种方法是,考虑kruskal算法,我们将边集按照边的权重进行分组,第i组就是所有权重为i的边组成的集合。注意到我们可以通过变换同组边之间的处理顺序得到所有的最小生成树。我们可以证明不管如何打乱同一组边的处理顺序,在处理完第i组的边后,所有的连通关系都是相同的,即在一种顺序下两个顶点是连通的,那么在另外一种处理顺序下亦然。这个可以利用归纳法进行证明,假设处理完前i-1类边后连通关系相同。那么考虑以任意顺序处理第i类边,如果第i组边中所有边最后都被加入最小生成树,那么连通关系肯定相同。如果不是所有边被加入,那么考虑任意一个未被加入的边(a,b),那么我们可以断定a和b一定连通(否则我们可以将(a,b)纳入最小生成树中)。因此当我们处理完前i类边后,对于任意前i类边(u,v),(u,v)一定是连通的。
而一条边是否有机会组成最小生成树,假设边的权重为k,那么我们只要观察,在处理完前k−1类边后,这条边的两个端点是否连通即可。第二种方法的时间复杂度也是O((n+m)log2n),但是这边不需要用到LCT,只需要用到并查集即可。
求最小生成树的数目
最小生成树的数目可以通过kruskal算法求出。在求所有最小生成树的并集这一节中,已经说过了所有kruskal算法流程中,可以通过变换边的处理顺序得到所有的最小生成树。事实上,对于固定的图,要得到最小生成树,其中所有权重为i的边选取的数量也是固定的(和边的处理顺序无关)。假设最小生成树中权重为i的边共选择了ci个,那么我们在处理第i类边的时候,可以暴力枚举选取ci条边的,情况,去除其中成环的情况。不同权重的边可以独立处理。
限制度数的最小生成树
限制一个顶点度数的最小生成树
给定一副图G=(V,E),找到其中一株生成树,要求顶点1的度数正好为k。如果有多棵满足条件的生成树,则找到总边权最小的生成树。
有两种求解算法(都没有很严谨的证明,拿着用吧,反正这种类型的题目很少)。
记f(k)表示我们的答案,可以感性发现f是个上凸函数。因此为了强制选k个,可以套上WQS算法,这样时间复杂度就是O(Elog2E+tE),其中t为二分次数。
还有一种更好的算法,他可以帮助我们同时找到f(1),…,f(k)(如果我们不要求度数正好为k,而是要求不超过k,这时候这种技术就非常有用了)。首先我们假设图是连通的(否则一定无解),我们先删除所有关于顶点1的边,之后算出最小生成森林,假设除了1所在连通块外,还有m个连通块,那么我们知道1至少度数为m,否则无法形成生成树。而得到f(m),我们可以简单贪心的为每个连通块选择一条最小权重边。接下来假设我们已经找到1度数正好为k−1的最小生成树Tk−1,要求1度数正好为k的最小生成树Tk。这时候需要加入一条边(1,v),同时假设原先v所在连通块中与1相关的边为(1,u),那么还需要删除v到u路径上权重最大的一条边ev,来避免出现环。可以考虑所有的v,而ev可以通过以u为根dfs得到。切换的时间复杂度为O(V)。因此总的时间复杂度为O(Elog2E+kV)。
提供一道题目。
限制k个顶点度数的最小生成树
给定n个顶点和m条带权无向边,现在要求为前k个顶点加上度数限制,第i个顶点的度数限制为di。要求判断是否存在一个生成树,如果存在则找到最小生成树。
其中1≤n≤50,0≤k≤5,m≤n2。
我们可以发现被枚举的顶点之间仅有(52)=10条边,只有210=1024个可能的导出子图,但是实际上只会有300个导出子图是无环的。
我们可以暴力枚举最小生成树的前k个顶点的生成子图。将剩余的边分成两类,第一类为所有一个顶点编号小于等于k的边,记作A,以及两个端点编号都大于k的边,记作B。
我们可以给每个顶点一个颜色,以及每个颜色一个数量限制。一个集合是独立集当且仅当每种颜色的元素都不超过该颜色的容量。这满足拟阵中独立集的定义。
现在我们需要找到图拟阵和颜色拟阵的一个拟阵交中的最大权最大集合。
一般最大权最大独立集可以O(r2x2)实现,其中r表示最大独立集的大小,而x表示全集大小。这里r=O(n),x=m,总的时间复杂度为O(n6),过大了。这里我们可以考虑一个优化,就是B中我们只需要考虑最小生成树中出现的O(n)条边,而其余边是不可能被考虑的。因此总的边数减少到O(kn)。总的时间复杂度为O(k2n4)。配合上总共运行的轮次,时间复杂度大致为O(300k2n4)。实际时间大概在3s左右。
二进制转换问题
给定n和m,现在有n个二进制数0,…,2n−1。如果两个二进制数之间只有一位不同,则在它们之间连接一条边。接下来处理m个请求。请求分两类:
- 给定l,r,要求删除所有值在l,r之间的二进制数。
- 给定a,b,保证a,b不同且未被删除,要求判断是否存在一条a,b之间的路径,不经过任意一个删除过的二进制数。
其中1≤n≤60,且1≤m≤105。
讲一下具体的做法。
我们可以将每个删除操作视作一种上色操作,如果一个元素被删除多次,则仅使用第一种颜色。接下来可以发现仅存在O(m)个连续段拥有相同的颜色。我们接下来考虑一个分治流程。假设分治的部分为0到2k−1,如果分治需要处理的连续段只有一个,那么可以直接返回这个连续段。否则我们按照第k−1位是否为1,分为两部分。之后递归处理两部分,之后通过双指针的技术将两部分中有重合(不考虑第k−1位)的连续段之间加入一条边。
接下来我们逆向处理问题,初始有些连续段不存在,之后不断插入这些连续段。插入的时候顺带尝试恢复所有相关的边,我们可以用个DSU来维护连通性。
记N为得到的连续段数,M为插入的边数,很显然最终的时间复杂度为O(nN+M+mlog2N)。
其中N=O(nm),M=O(nN)=O(n2m)。这时候时间复杂度为O(n2m+mlog2nm)。
但是实际上我们可以压缩顶点数。具体原理是可以发现[0,x]中任意两个元素始终可达(先化成0,之后转成任意数),同理对于[x,2k−1]中任意两个数也是可以互达的。具体是对于连续段[l,r],如果l=r,则显然是一个连续段,不用分割,否则我们按照l,r的第一个不同位进行分割,划分为两个连续段。这样最后顶点数为O(m),而边数为O(nm)(边可以用相同的分治算法来求边)。
最后总的时间复杂度为O(nm+mlog2m)。