强连通分量
强连通图判定
从一个点出发,可以遍历整张图,再将所有的边反向,从同一点出发,可以遍历整张图,则该图是强连通图
Tarjan求有向图强连通分量
我们在dfs树上,当一个点的
Code:
int dfn[N],low[N],ins[N],stk[N],top,ts,tot,scc[N];
inline void tarjan(int u)
{
dfn[u]=low[u]=++ts;
ins[u]=1;
stk[++top]=u;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(ins[v]) low[u]=min(low[u],dfn[v]);
}
if(low[u]==low[v])
{
int x;++tot;
do{
x=stk[top--];
scc[x]=tot;
ins[x]=0;
}while(x!=u);
}
}
Trick:出栈顺序为拓扑序的倒序
Kosaraju算法
算法流程
先将图
证明
对于两个点
HAOI2006 受欢迎的牛
题面
给定
题解
求强连通分量,缩点。如果只有一个出度为
ZJOI2007 最大半连通子图
题面
有一个
求出最大的
题解
缩点,要求的最大半联通子图一定是DAG上的一条链,分别记
割点
定义
无向图中,删掉该点后使得图不连通,则该点为割点
求法
对于一个点
- 如果该点是根,那么儿子数
就是割点 - 如果该点不是根,那么当
时该点就是割点
Code:
int dfn[maxn],low[maxn],ts,rt;
std::vector<int> cut;
void tarjan(int u){
dfn[u]=low[u]=++ts;
int son=0;
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
++son;
tarjan(v);
low[u]=min(low[u],low[v]);
if((u==rt&&son>1)||(u!=rt&&low[v]>=dfn[u]))
cut.push_back(u);
}
else low[u]=min(low[u],dfn[v]);
}
}
桥
定义
无向图中,删掉一条边使得图不连通,这条边就是桥
求法
对于一条边
Code:
int dfn[maxn],low[maxn],ts,rt;
std::vector<pii> bridge;
void tarjan(int u){
dfn[u]=low[u]=++ts;
for(int i=head[u];~i;i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>dfn[u])
bridge.push_back(mkp(u,v));
}
else low[u]=min(low[u],dfn[v]);
}
}
变双联通分量/无向图缩点
边双连通分量
删掉该点集中任意一条边都不会影响该点集的连通性,则称该点集为一个边双强连通分量
Code:
inline void tarjan(int u,int fa)
{
low[u]=dfn[u]=++ts;
ins[u]=1;
stk[++top]=u;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa) continue;
if(!dfn[v]) {
tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
int x;++tot;
do{
x=stk[top--];
ins[x]=0;
dcc[x]=tot;
}while(x!=u);
}
}
DFS树求割点/桥
对于每一条非树边
动态加边维护桥
上面那个做法可以在线完成
可以使用一个并查集维护当前双联通分量中的点,记录一下每个双联通分量中最高的点。
然后对于一条非树边,暴力将这些点合并起来即可
因为一条边最多被合并一次,需要不超过
点双联通分量/圆方树
点双连通分量
删掉点集中的任意一个点,该点集仍然连通,则称该点集是一个点双连通分量
圆方树
原图中所有的点作为圆点,对于每一个点双,我们建立一个方点,让这个方点连接点双中的每一个点,那么所有的度数大于
实际题目中,可以给圆方树的圆点/方点赋上不同的权值,再通过dp、树剖等方式来计算答案
Code:
int dfn[N],low[N],ts,tot,stk[N],top,val[N],sum;
vector<int> G[N];
inline void tarjan(int u)
{
++sum;
dfn[u]=low[u]=++ts;
stk[++top]=u;
val[u]=-1;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u])
{
int x;++tot;
do{
x=stk[top--];
++val[tot];
G[x].emplace_back(tot);
G[tot].emplace_back(x);
}while(x!=v);
++val[tot];
G[u].emplace_back(tot);
G[tot].emplace_back(u);
}
}
else low[u]=min(low[u],dfn[v]);
}
}
小Trick
给你一个图,每条边都只在一个环内,等价于每个点双联通分量是一条边或者一个环
POJ 3177 Redundant Paths
题面
给定
题解
缩点,以任意一个度数大于
POI2008 BLO-Blockade
题面
给定
题解
对于每一个点
- 如果
不是割点,那么就会产生以 对与 有关的不连通点对 - 如果
是割点,设删掉与 有关的边之后产生了 个连通块,那么答案就为考虑优化,我们有
计算答案即可
HNOI2012 矿场搭建
题面
给定
题解
建立圆方树,对于所有叶节点,它们不是割点,不会影响连通性,如果该点为割点那么设
- 该点是圆点,那么有其中当
时, - 该点是方点,那么有
P4630 [APIO2018] 铁人两项 (圆方树上dp)
题解
考虑到当我们固定
进一步考虑,
所以建出原图的圆方树,将方点的权值设为这个点双中圆点的个数,将圆点的权值设为
我们设
- 点
是圆点时,假设它有 棵子树,它的子树对答案的贡献是
看上去这个转移是
所以我们每次记一个前缀子树和,然后乘上下一个子树的大小进行转移,这是
- 当
是方点,如果 不在这个点双内,那么其贡献一定被这个点双中的圆点中就已经统计过了,所以考虑如何计算 在点双内的方案数
- 如果
中只有一个在点双中,那么 可以选的位置就有 个,总共有 次贡献,但是当 选在了割点处,那么 就无处可选了,乘的另外一部分是相同的,所以整个就少选了 次,所以总次数就变成了 - 如果
都在点双内部,显然可以选择的位置就有 个
所以在上面圆点的转移乘上
posted on 2023-09-11 19:55 star_road_xyz 阅读(9) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】