仙人掌是知足如下两个限制的图:node
其中第二个限制让仙人掌的题作起来十分舒服。api
首先勾出一棵有根生成树。
那么树边上正常转移便可。
咱们把返祖边造成的环归到环上深度最浅的点上,即环顶。
那么到环顶时,单独跑一遍关于环的\(DP\)便可。
通常写法为:数据结构
void dfs(RG int u,RG int From) { dfn[u] = low[u] = ++ oo ; fa[u] = From ; for(RG int i = head[u] ; i ; i = t[i].next) { RG int v = t[i].to ; if(!dfn[v]) dfs(v , u) , low[u] = min(low[u] , low[v]); else if(v != From) low[u] = min(low[u] , dfn[v]) ; if(low[v] > dfn[u]) 正常的树形DP(F(v) --> F(u))。 } for(RG int i = head[u] ; i ; i = t[i].next) if(fa[t[i].to] != u && dfn[t[i].to] > dfn[u]) 基环DP(u,v)。 }
分清楚\(low\)、\(dfn\)的含义便可。
因为记录了\(fa_u\),基环DP中的扣环也很是容易:函数
tot = 0 ; for(RG int x = v; x ^ u; x = fa[x]) q[++tot] = x ; q[++tot] = u ;
题意:求仙人掌的最大独立集。
题解:作一遍正常的树形\(DP\),遇到环则把环拉出来单独作一遍。ui
题意:求仙人掌的直径。
题解:
一样的作正常树形\(DP\),碰到环顶则把环拉出来。
问题变为在环上选两个点,使其权值与距离和最大。显然单调队列便可。spa
圆方树是基于仙人掌的一种特殊数据结构。
其中圆点即图中原来的点,方点则表明一个点双。
咱们沿用上面处理仙人掌\(DP\)的作法。
若是是生成树上的边,则直接相连。
不然,把环抠出来,为这个环新建一个方点,将环上的点与此方点连边。
板子与上面的仙人掌DP基本同样就不放了。
那么咱们就能够在圆点上维护本来图单点信息,方点上维护点双信息了。code
题意:询问\(Q\)次,每次询问仙人掌上两点的最短路。
题解:
考虑建出圆方树,方点向圆点的连边 边权为此点到环顶的最短距离。
那么查询两点\((u,v)\)时,咱们求出其\(lca\),而后讨论:队列
对于任意图,相似圆方树,能够创建出广义圆方树。
广义圆方树与圆方树的差异在与,特别的,对于两个点的联通量,也创建一个方点。
因此广义圆方树上只有圆-方边。
创建的方法与普通圆方树建法仍是有所不一样:游戏
void Tarjan(int u,int From) { low[u] = dfn[u] = ++ oo ; stk[++Top] = u ; for(int e = G1.head[u] ; e ; e = G1.t[e].next) { if(e == (From ^ 1)) continue ; int v = G1.t[e].to ; if(!dfn[v]) { Tarjan(v , e) ; low[u] = min(low[u] , low[v]) ; if(low[v] >= dfn[u]) { G2.add(++N , u) ; int x = 0 ; blg[N] = sum ; do { x = stk[Top] ; G2.add(x , N) ; Top -- ; }while(x ^ v) ; } } else low[u] = min(low[u] , dfn[v]) ; }return ; }
特别要注意重边的问题(原图中不能有重边或自环),一样时刻分清\(low\)、\(dfn\)的做用便可理解。get
题意:给定一张图与一些点对路径,对于每一个点,求出必须通过此点的点对路径的数量。
题解
把广义圆方树建出来,那么一个点对路径上必须通过的点即圆方树上对应路径的圆点。
直接在广义圆方树上差分一下就好了。
题意:给定一张图,两种操做:修改点权 或者 询问两点路径上的点权最小值。
题解
首先把广义圆方树建出来,考虑用方点维护\(SCC\)中的点权最小值。
可是这样建的话,修改一个圆点时须要把与其相连的方点所有修改一遍。
这很容易被卡成\(O(n^2)\)。
因此对于方点,咱们不维护对应的环顶(即广义圆方树中方点的父亲)。
这样修改的时候,咱们就只用修改父亲。
查询时,若是两点的\(lca\)为方点,额外与\(lca\)的父亲(环顶)取\(min\)便可。
题意:求有多少点对\((s,c,f)\),知足存在一条u -> c -> f且路每一个点只通过一次的路径 的个数。
题解
显然枚举一下\(c\),而后算它的贡献。
考虑广义圆方树上从一个圆点出发的不重复路径有哪些。
除了普通的树上路径外,还能够在与此点处于同一个方点的那些点中选出两个。
这个好像DP一下就好了?
两遍dfs,第一遍考虑儿子的贡献,第二遍考虑父亲的贡献。
大力讨论一下,第二边dfs的时候记得删去当前子树的贡献,简单转移就好了。
其实与上面的图论没有半毛钱关系......
虚树是指在原树上选出一些点构出一棵新树,并保证新树与原树形态、性质相同。
通常来讲,对于多组询问,若 \(\sum\) 点数 较小,
那咱们不如对于每次询问构出虚树,而后就能够\(O(n)\)进行DP啦。
其实比较简单,注意父子关系的保持便可。
首先对原树进行一遍\(dfs\)搞定全部点的 \(dfs\)序\(dfn\) 与 子树结尾\(ed\) 。
那么对于每次询问,咱们把这些点抠出来,而后:
代码以下:
IL bool cmp(int a , int b) {return dfn[a] < dfn[b] ; } IL void Build(){ dfs(1 , 0) ; //求 dfn 与 ed get_query_node , put it in 'p[]' . sort(p + 1 , p + n + 1 , cmp) ; for(int i = 1; i < n; i ++) p[i + n] = LCA(p[i] , p[i + 1]) ; p[2*n] = 1 ; n = n * 2 ; sort(p + 1 , p + n + 1 , cmp) ; Top = 0 ; for(int i = 1; i <= n; i ++) { while(Top && ed[stk[Top]] < dfn[p[i]]) -- Top ; if(Top) G2.add(stk[Top] , p[i] , E<stk[Top],p[i]>) ; stk[++ Top] = p[i] ; }return ; }
虚树上的边压缩了原树上的路径信息。
特别注意,因为咱们会在虚树的边上压缩信息以供后续处理,
而咱们为了保证虚树联通因此可能额外引入了原树的根。
因此必定要考虑额外引入点对答案的影响,如有影响,则要删去其贡献!(高频错点)
如下题目的通性:多组询问,总点数 \(\leq\) \(n\)。
题意:一棵树,边有代价,每次询问要求删去最小代价的边集,使得关键点不能到达根结点。
题解:
每次询问建出虚树,考虑如何\(O(n)\)进行\(DP\)。
貌似记录一会儿树内有无关键点,而后一路向上选择切掉当前边或累加儿子子树最优解便可。
题意:
一棵树,边有边权,每次询问给出\(K\)个点,在它们之间修\(\binom{K}{2}\)条边。
每次要求回答三个问题:(1)最长路 ; (2)最短路 ; (3)全部路径的长度和是多少。
题解:
每次询问建出虚树。
最短路最长路基础的直径DP便可。路径长度和考虑一下通过这条边的点对数就好了。
特别注意最短路与最长路DP时,
起点与终点位置只能是询问点(不能是引入点),这个每一个点附初值时特殊处理一下就好了。
题意:一张图,每次询问给定点集,问有多少个点知足删去后使得点集中任意两点不联通。
题解:
看到图上连通性问题直接上圆方树(那是什么?上面就有......)
把圆方树建出来,而后再建圆方树的虚树。
那么答案就是"虚圆方树"上除了询问关键点外的圆点个数,直接建树是统计便可。
题意:
定义原树上的一个点被其离的最近的关键点管辖。若距离相同则选择编号小的。
对于每次询问,给出一些关键点,试输出每一个关键点管辖的点的数目。
题解:
首先把关键点建出虚树。
那么咱们是能够O(n)DP出这些点的归属是吧。
这是经典的DP了,两遍dfs,第一遍DP出儿子最优值,第二遍再考虑父亲。
而后考虑哪些没有在虚树中出现的点(都压缩在边上)。
对于虚树上的边,咱们分状况讨论:
若边的两端被同一个点管辖,那么显然这条边上全部的点都归那个点管辖。
若是两端的点被不一样的点管辖,那么必定存在一个分界线。
咱们能够倍增二分找到那个分界线,而后给两个关键点加上对应贡献便可。
题意:
给定一棵 \(n\) 个节点的树,个点有一个权值 \(a[i]\) ,保证 \(a[i]\) 是一个 \(1..n\) 的排列。
求 \[\frac{1}{n(n-1)}\sum_{i=1}^n\sum_{j=1}^n\varphi(a_i)·\varphi(a_j)·dist(i,j)\]
其中, \(\varphi(x)\) 是欧拉函数, \(dist(i,j)\) 表示 \(i,j\) 两个节点在树上的距离。
题解:
首先欧拉函数有个公式:\(\varphi(a*b) = \varphi(a)\varphi(b)\frac{d}{\varphi(d)}\),其中\(d=gcd(a,b)\)。
因此式子化一化?
\[Ans = \frac{1}{n(n-1)} \sum_{d=1}^n \frac{d}{\varphi(d)} \sum_{i=1}^n \sum_{j=1}^n\ [gcd(a_i,a_j)=d]\ \varphi(a_i)\varphi(a_j)dist(i,j)\]
考虑后面这一坨东西:
\[f(d) = \sum_{i=1}^n \sum_{j=1}^n [gcd(a_i,a_j)=d]\ \varphi(a_i)\varphi(a_j)dist(i,j)\]
一种莫比乌斯反演既视感啊.....定义:
\[F(d) = \sum_{d|i} f(i) = \sum_{i=1}^n \sum_{j=1}^n [d|gcd(a_i,a_j)]\ \varphi(a_i)\varphi(a_j)dist(i,j)\]
那么\(f(d) = \sum_{d|i} \mu(\frac{i}{d})F(i)\)。怎么求\(F(d)\)?
貌似若是能\(O(n)\)求那么总复杂度就是调和级数啊?
因此枚举\(d\),而后把知足\(a_i = kd\)的点拿出来进行虚树DP。
\[E(d) = \sum_{i=1}^n \sum_{j=1}^n\varphi(a_i)\varphi(a_j)(dis_i + dis_j - dis_{LCA(i,j)})\]
维护一会儿树内的\(\sum \varphi(u)\) 与子树内的\(\sum dep_u\varphi(u)\),而后在\(LCA\)处统计答案便可。
终于写完啦(QwQ)。 写了这么多,就是为了证实学了这些毒瘤玩意后我还活着......