强连通分量

强连通图判定

从一个点出发,可以遍历整张图,再将所有的边反向,从同一点出发,可以遍历整张图,则该图是强连通图

Tarjan求有向图强连通分量

dfn[u] 表示点 u 的dfs序,low[u] 表示点 u 可以走到的dfs序最小的点

我们在dfs树上,当一个点的 low[u]=dfn[u] ,即它无法进一步向上走时,我们就找到了一个强连通分量

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算法

算法流程

先将图 G 所有的边反转得到图 G ,计算 G 的拓扑序并按照 G 的拓扑序对 G 进行深度优先搜索

证明

对于两个点 x,y ,如果在图 G 中存在 xy ,拓扑序中 xy 前面,即 G 中存在 xy ,即 G 中存在 yx ,因此 x,y 在同一强连通分量中

HAOI2006 受欢迎的牛

题面

给定 n 个点 m 条边的有向图,求出能够被所有点到达的点

n1×104,m5×105

题解

求强连通分量,缩点。如果只有一个出度为 0 的点,那么这个点就是答案;如果这个数量大于等于 2 ,那么无解

ZJOI2007 最大半连通子图

题面

有一个 n 个点 m 条边的有向图,你要找一个点集 S,使得 u,vS 中有 u 能到达 v 或者 v 能到达 u
求出最大的 S,然后求有多少种选法

n105,m106

题解

缩点,要求的最大半联通子图一定是DAG上的一条链,分别记 f[i],g[i] 表示大小和方案数,dp即可

割点

定义

无向图中,删掉该点后使得图不连通,则该点为割点

求法

对于一个点 u 和它的一个儿子 v

  1. 如果该点是根,那么儿子数 2 就是割点
  2. 如果该点不是根,那么当 low[v]>=dfn[u] 时该点就是割点

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]);
	}
}

定义

无向图中,删掉一条边使得图不连通,这条边就是桥

求法

对于一条边 uv,如果 low[v]>dfn[u] ,则该边就是桥

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树求割点/桥

对于每一条非树边 (u,v) ,让 u+1 ,让 v1 (注意这里一定是返租边),那么就可以求出每一边/点的覆盖次数,覆盖次数为 0 的就是割点/桥

动态加边维护桥

上面那个做法可以在线完成

可以使用一个并查集维护当前双联通分量中的点,记录一下每个双联通分量中最高的点。

然后对于一条非树边,暴力将这些点合并起来即可

因为一条边最多被合并一次,需要不超过 O(m) 次的并查集操作

点双联通分量/圆方树

点双连通分量

删掉点集中的任意一个点,该点集仍然连通,则称该点集是一个点双连通分量

圆方树

原图中所有的点作为圆点,对于每一个点双,我们建立一个方点,让这个方点连接点双中的每一个点,那么所有的度数大于 1 的圆点都是割点

实际题目中,可以给圆方树的圆点/方点赋上不同的权值,再通过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

题面

给定 n 个点 m 条边的无向图,求至少需要加多少条边使得整张图成为边双联通分量

n1×104,m5×104

题解

缩点,以任意一个度数大于 1 的点为根,记叶子数为 l ,答案即为 l2

POI2008 BLO-Blockade

题面

给定 n 个点 m 条边的无向图,求将与每个点四周的边去掉之后有多少对点不连通 ((x,y),(y,x) 算两种情况)

n1×105,m5×105

题解

对于每一个点 i

  1. 如果 i 不是割点,那么就会产生以 2(n1) 对与 i 有关的不连通点对
  2. 如果 i 是割点,设删掉与 i 有关的边之后产生了 k 个连通块,那么答案就为

    2(n1)+i=1kj=1,jiksiz[i]×siz[j]

    考虑优化,我们有

    j=1,jiksiz[j]=nsiz[i]1

计算答案即可

HNOI2012 矿场搭建

题面

给定 n 个点 m 条边的无向图,要求从中选出最少的关键点,使得无论删除哪个点,其余的点都存在通往关键点的路径,求出关键点数量和方案数

n500,m1000

题解

建立圆方树,对于所有叶节点,它们不是割点,不会影响连通性,如果该点为割点那么设 f[u] 表示删掉点 u 时其子树内最少的关键点数量,g[u] 表示方案数,分圆方两种情况讨论

  1. 该点是圆点,那么有

    {f[u]=vsonumax(f[v],1)g[u]=vsonug[v]

    其中当 f[v]=0 时,g[v]=siz[v]
  2. 该点是方点,那么有

    {f[u]=vsonuf[v])g[u]=vsonug[v]

P4630 [APIO2018] 铁人两项 (圆方树上dp)

题目

题解

考虑到当我们固定s,f时,我们c的选择就是sf所有简单路径的并,然后减去2(因为c不可以选在s,f处)

进一步考虑,sf简单路径的并就是路径上点双大小的并

所以建出原图的圆方树,将方点的权值设为这个点双中圆点的个数,将圆点的权值设为1,那么从sf简单路径的并就是圆方树上sf的路径

我们设 fi 表示 s,fi 子树中的方案数,考虑枚举中转点 c,分两种情况转移

  • c 是圆点时,假设它有 4 棵子树,它的子树对答案的贡献是

S1S2+S1S3+S1S4+S2S3+S2S4+S3S4

看上去这个转移是 O(n2) 的,是我们可以这么做

S1S2+(S1+S2)S3+(S1+S2+S3)S4

所以我们每次记一个前缀子树和,然后乘上下一个子树的大小进行转移,这是O(n)

  • c是方点,如果s,f不在这个点双内,那么其贡献一定被这个点双中的圆点中就已经统计过了,所以考虑如何计算s,f在点双内的方案数
  1. 如果 s,f 中只有一个在点双中,那么 c 可以选的位置就有 deg1 个,总共有 deg×(deg1) 次贡献,但是当 f 选在了割点处,那么c就无处可选了,乘的另外一部分是相同的,所以整个就少选了deg 次,所以总次数就变成了 deg×(deg2)
  2. 如果 s,f 都在点双内部,显然可以选择的位置就有 deg2

所以在上面圆点的转移乘上 deg2 的系数就行了

posted on   star_road_xyz  阅读(9)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示