- (前置知识)Boruvka
- Kruskal 重构树
- 分层图选讲(包括斯坦纳树)
- 竞赛图 .5. 杂项:树哈希,弦图,支配树
Boruvka
对每个当前连通块,找它和别的连通块之间的边中,边权最小的。
然后把所有找到的边都加入最小生成树。不会成环。
复杂度 。
Kruskal 重构树
每一次加边会合并两个集合,我们可以新建一个点,点权为加入边的边权。
同时将两个集合的根节点分别设为新建点的左儿子和右儿子,将新建点设为根。
在进行 轮之后我们得到了一棵恰有 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子,这就是 Kruskal 重构树。
OIWIKI 的图:

是不是太特殊了,来个更特殊的。

性质:原图中两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = Kruskal 重构树上两点之间的 LCA 的权值。
P4768 [NOI2018] 归程
这道题问的是路径的最小和。
我们发现前一段的海拔大于当天的水位线,这个限制我们可以用 Kruskal 重构树 去解决一下。我们需要找到所有能到达的点,也就是重构树上的一个子树。
然后答案就是从可以到达的点集中距离 最近的。
利用性质:边的边权单调。
P13548 [OOI 2022] Air Reform
思路
这道题给出了图 G,我们需要在 G 的反图上的最小生成树进行查询。
首先,我们先考虑对原图作最小生成树。我们发现 Kruskal 中,每次连接两个联通分量,然后连接它们的边权 是单调不降的。
所以我们考虑在这个过程中计算反图的最小生成树。我们连接原图的边时,合并的连通块的点需要在反图上也合并一下。
那我们需要怎么合并?需要暴力启发式合并。
简单总结一下,对于瓶颈路问题,最小生成树可以替代 Kruskal 重构树,但是“只能走边权 ”这种问题必须要用。
CF1583H Omkar and Tours
使一个节点和另一个节点 LCA 的 dfs 序最小,另一个节点选 dfn 最小或最大的节点。
思路
这道题直接利用重构树上的 LCA 就是路径权值最大值这个性质做就行了。
对于第二问,只需要求出 dfn 最小的和最大的叶子和这个节点的 LCA。
分层图
P4568 [JLOI2011] 飞行路线
每走一层就是用了一次免费搭乘飞机的机会。
P3831 [SHOI2012] 回家的路
分层,横纵各是一层,然后层间就是换乘站。特殊的,起点和终点换乘不需要时间。
P9370 [APIO2023] 赛博乐园 / cyberland
P9370 [APIO2023] 赛博乐园 / cyberland。
差不多,就是用一次就走一层。
这道题比较独特的就是清零了就相当于可以从这个点重新开始。
代码不好写,vector 建图还被卡常了。
斯坦纳树
给定一个图,需要求一个最小子图,包含给定的点,并且联通。给定的点很少,需要状压。
P6192 【模板】最小斯坦纳树
考虑状压 ,设 表示以 为根的答案中包含集合 中所有点的最小边权值和。
我们的两个转移分别覆盖了加边和加点两种情况,所以完整。
时间复杂度 。
P4294 [WC2008] 游览计划
这道题求的是点权,所以需要改一下。
思路
然后 多加了,需要减回去。
确实,这题的难点并不在于如何求出答案,而在于打印方案。
打印方案就是最后 dfs 一遍,看看需要从哪里转移。
注意点的范围是 而不是 。
P3264 [JLOI2015] 管道连接
每个情报站有一个特定的频道,需要 使得任意相同频道的情报站之间都建立通道连接。
这道题是求完斯坦纳树的 dp 之后再进行一次 dp。
思路
表示“使得状态 中所有颜色的要求都被满足”的最小花费。
中间需要用一个 表示连接 这个子集的花费。
这个 dp 可以通过转移多个子集连一起的情况,可以计算公用连边。
树哈希
P5043 【模板】树同构 / [BJOI2015] 树的同构
P5043 【模板】树同构 / [BJOI2015] 树的同构。
这道题找到重心,然后计算哈希就行了。
P4323 [JSOI2016] 独特的树叶
刚才的哈希可能稍显复杂,看到题解中还有这种:
用...质数表。
思路
我们先用换根 dp 求出 所有节点的哈希值,然后再求出 的。
对于 的每一个根,只需要枚举删去的根节点,就行了,删去根节点还是很好算的。
令 为 子树中去除了 的哈希值
P8499 [NOI2022] 挑战 NPC Ⅱ
思路
我们发现 很大, 很小。然后这道题的根节点又是固定的。
所以我们先把没用的地方删掉,也就是已经相同的子树删掉。
所以最后我们最多剩下 个分支,暴力匹配复杂度差不多 。
实现的话我们计算 表示能否使 和 匹配。
弦图
弦图:没有长度 的纯环的图。
性质:区间图是弦图。看到最大独立集(团)、染色数等问题,可以猜这是一个弦图。
完美消除序列
定义
单纯点:邻集加上自己是团。
完美消除序列: 在 的导出子图内是单纯点。
是弦图 等价于 有完美消除序列。
线段图是弦图。
最大势算法
用来求完美消除序列的, 但是可以 写着更方便。
从 到 给点标号(即倒序填完美消除序列),每次选择最多邻居是 已标号的点的点标号。
然后判断这个是不是就行了。
性质
Lemma 1:
团数 色数
证明:考虑单独对最大团的导出子图(就是选取一些点,然后连边原图有的新图也要有)进行染色,至少需要 种颜色。
P14506 【模板】弦图
这道题直接用最大势算法判断。
然后写法的话就是对于每一个点,看它的后继与剩余的相邻的点是否有连边就行了,可以理解为具有传递性吧,就是其它情况前面的节点已经判断过了。
-
最大团大小:对每个点,它加上它后面邻居的个数,取最大值。因为每个点与后面邻居构成团。
-
色数:因为色数大于等于最大团的大小,然后构造等于,一种构造方法:按完美消除序列从后往前贪心染色:每个点的后继邻居构成一个团,最多用 种颜色,因此总能从 中选到可用颜色,从而 。
-
最大独立集:贪心:按完美消除序列从前到后,如果一个点没有被已选独立集中的点相邻,就选它,并标记其邻居不可选。简单理解一下,就是当前点和它后面连边的都在最大团里面,选了一定没有当前优。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
vector<int>e[200010],b[200010],l[200010];
int vis[200010],xl[200010],p[200010],h[200010],c[200010];
bool check(){
for(int i=1;i<=n;i++){
for(int v:e[i])if(p[i]<p[v])l[i].push_back(v);
}
for(int i=1;i<=n;i++){
if(l[i].size()<2)continue;
int u=l[i][0];
for(int j=1;j<l[i].size();j++){
int v=l[i][j];
auto it=lower_bound(e[u].begin(),e[u].end(),v);
if(*it!=v)return 0;
}
}
return 1;
}
void solve(){
cin>>n>>m;
for(int i=1;i<=n;i++)e[i].clear(),b[i].clear(),l[i].clear();
for(int i=1;i<=n;i++)vis[i]=h[i]=c[i]=0;
for(int i=1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
for(int i=1;i<=n;i++)sort(e[i].begin(),e[i].end());
for(int i=1;i<=n;i++)b[0].push_back(i);
int now=0;
for(int i=n;i>=1;i--){
int x=-1;
while(1){
while(!b[now].empty()){
int v=b[now].back();
b[now].pop_back();
if(!vis[v]){
x=v;
break;
}
}
if(x!=-1)break;
now--;
}
vis[x]=1,xl[i]=x,p[x]=i;
for(int v:e[x]){
if(vis[v])continue;
h[v]++;
b[h[v]].push_back(v);
if(h[v]>now)now=h[v];
}
}
if(!check()){
cout<<"No"<<endl;
return;
}
cout<<"Yes"<<endl;
for(int i=1;i<=n;i++)cout<<xl[i]<<' ';
cout<<endl;
int ans=0;
for(int i=1;i<=n;i++){
int res=1;
for(int v:e[xl[i]])res+=(p[v]>p[xl[i]]);
ans=max(ans,res);
}
cout<<ans<<' '<<ans<<' ';
ans=0;
for(int i=1;i<=n;i++){
int u=xl[i],pd=1;
for(int v:e[u]){
if(c[v]){
pd=0;
break;
}
}
if(pd){
c[u]=1;
ans++;
}
}
cout<<ans<<endl;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int T;
cin>>T;
while(T--){
solve();
}
return 0;
}
支配树
支配树:给定有向图 和根 ,支配树满足 的子树恰好是那些删去 后 不可达的点。
DAG 上的支配树:P2597 [ZJOI2012] 灾难
在 DAG 上求支配树然后求节点 size 即可。
例题:P7520 [省选联考 2021 A 卷] 支配
思路
先暴力建出支配树,也就是枚举每一个点,看看删除它之后 能到达哪些点。对于每个点,在其支配点集中找到距离它最近的那个点作为父亲。
然后对于询问 ,我们发现这条边必须走。所以会影响到的点就是支配树上父亲不在 上的。
但是有点小问题,就是我们也需要标注 到父亲路径上的点的直接子节点,因为不管走哪条路,都要经过父亲,所以支配节点不变。
有点卡常,入队立刻标记 。
普通支配树:P5180 【模板】支配树
Lengauer-Tarjan 算法
引入半支配点:一个点 的半支配点是指一个 dfs 序最小的点 ,使得 可以通过一条路径 到达点 ,求满足对于任意 ,有 。
对于一个可以直接到达点 的点 :
-
若 则 可能是 的半支配点。
-
若 ,,则考虑其所有祖先 满足 , 的半支配点可能是点 的半支配点。
按照 dfs 序考虑 。第一种很好算,第二种用值域并查集,慢慢合并,表示到根的路径上半支配点 DFS 序最小的节点。这里面的节点 dfs 序一定更大,因为是倒序。
然后需要利用半支配点求解支配点。
对于每个节点 ,考虑从半支配点到它的路径(不含半支配点。dfs 树上的路径)上,半支配点的 DFS 序最小 的那个节点,记为 。
如果 的半支配点等于 的, 就是支配点。
如果 的半支配点小于 的, 的支配点就是 的。就最后正序遍历的时候重新记录就行了。
竞赛图
竞赛图: 个点的有向图,任意两个点之间恰有一条有向边。
性质
缩点成链
竞赛图缩点后,所有 SCC 构成的东西呈链状,因此竞赛图上可以快速判断可达性。
链上的每个前后缀都是独立的“子竞赛图”。
关于哈密顿路径
存在一条哈密顿路径
考虑进行插点,插点进去。
对于原来的路径中,找出相对于新的点的出边,然后路径上下一个点是这个点的入边的位置插进去就行了。如果只有入/出就放开头/结尾。
竞赛图任意一个 SCC 存在一条哈密顿回路
同样是插点进去。
对于原来的路径中,先构造回路。
对于新的点,一定有入边和出边(不可能只有出边/入边连接这个点),那直接把这两个点之间的路径加上这个点就行了。
关于环的性质
-
竞赛图若有环一定存在三元环。
-
竞赛图的 个点的 SCC 中一定存在 元环。
-
有 个三元环(总-三个点中有一个点出度为 的情况)。
其它
- 若 的出度大于 的出度,则 可以到达 。
CF1498E Two Houses
若 的出度大于 的出度,则 可以到达 。
证明+思路
这是因为竞赛图缩点后形成链状结构。同一个 SCC 显然。否则如果 出度比 大, 就在 前面。
有了这个就可以直接做了。
注意 是入度。
兰道定理
我们定义比分序列为将每个点的出度 从小到大排序的序列。
那么若满足 且当 时取等(即 ),则一定能够造出一种竞赛图,反之不能。
必要性显然,考虑充分性证明,我们构造初始图,对于 则 连边,设此时比分序列为 ,这个序列显然在上述条件能够取到等号。保持 ,不断调整图直到 。
充分性怎么构造?
CF850D Tournament Construction
CF850D Tournament Construction。
思路
这道题 ,因为边数 。
表示枚举到 位的出度, 位出度为 ,出度和为 ,是否存在这样的方案。
如果 ,则合法,否则不合法。
然后考虑怎么构造。
对于 , 向 连边,得到一个最初始的竞赛图。初始出度序列 (升序),目标 已升序。
只要 :
- 找第一个位置 使 (说明 出度偏小)。然后找最后一个位置 使 。
- 找第一个位置 使 ( 出度偏大)。
- 可以证明 且 ,因此存在一个顶点 满足 且 。(反证法,如果不存在,所有被 指向的顶点都一定也被 指向,但是 不同)
- 翻转这两条边:改为 和 。结果 减 , 加 ,其余不变。
重复直到 。
兰道定理求强连通分量
分界点:
每个点出度为 。
s_{j}=\binom{i}{2}]$$ ### P3561 [POI 2017] Turysta [P3561 [POI 2017] Turysta](https://www.luogu.com.cn/problem/P3561)。 [人才!](https://www.luogu.com.cn/article/0m2hb68e)。 其实这篇题解是利用竞赛图两两之间必有边的性质。