蒟蒻的笔记本二、tarjan

diker

tarjan好神奇啊。

一、最大强连通分量问题

1、一些定义:

<1>强连通:在有向图G中,设有两个点u、v,发现由u有一条路可以到v,由v也有一条路可以到u,则u、v强连通。

<2>强连通图:每两个点都强连通的有向图叫强连通图。

<3>强连通分量有向图极大强连通子图。

2、思路:
当在一个有向图中找到了一条从u到u的回路,则在该路径上的所有点和u可以构成一个强连通子图,因为环上任意两点都可以互相到达,那么所有从u到u的回路上的所有点和u构成一个强连通分量。 (personal view)

3、算法:最简单的想法是dfs找回路,记录路径上的点,但图太大时时间空间上不够,需要加一些优化。tarjan算法就是基于dfs,使用栈,标记数组等来进行优化,使得时间复杂度在O(n)。

用到的两个辅助数组:

dfn[u]:记录点u是第几个遍历到的(只是标记遍历先后顺序)【时间戳】因为回路上的点本来是没有顺序可言的,dfn人为加了一个顺序。

low[u]:记录点u所在回路中可以到达的点中最小的时间戳
用栈储存强联通分量,时间戳越小越早入栈。

用vis[u]数组来记录u是否在栈中。

算法的具体步骤:

tarjan(u){//当遍历到u点时
 dfn[u]=low[u]=++ind;//先初始化u点的dfn和low都等于当前的时间戳
    s.push(u);//将u压进栈
    vis[u]=1;//标记u在栈中
    for(v:Edge(u)){//搜索u可以到达的所有点v
        if(!dfn[v]) tarjan(v);//如果v点还没有被遍历过,遍历v点
        if(vis[v]) low[u]=min(low[u],low[v]);//如果v点还在栈中,更新low[u]的值
        //因为如果v还在栈中则代表v在u的一条回路上,那么v可以到达的最早遍历的点(low[v])u点也可以到达
        //所以low[u]在low[u]、low[v]中取一个最小的
        //而如果v已经不在栈中了,说明v点到不了v点之前的任何点了,已经找完了v点的所以回路并且都已出栈
    }
 if(low[u]==dfn[u])//u所能到达的最早遍历的点就是u本身,而且u的回路全部都搜索完了
    //那么u点及栈中在u点上(比u后入栈)的点构成一个极大强连通分量
    do{
        vis[s.top()]=0;//标记不在栈中了
        s.pop();//出栈
    }while(vis[u]);//直到u点出栈为止
    //如果low[u]!=dfn[u],代表u是dfn[]==low[u]的点的回路上的一点,而这个点还没有拓展完所有回路
}

T1:洛谷P1726 上白泽慧音
题目链接
题目描述
在幻想乡,上白泽慧音是以知识渊博闻名的老师。春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄。因此慧音决定换一个能够聚集最多人数的村庄作为新的教学地点。人间之里由N个村庄(编号为1…N)和M条道路组成,道路分为两种一种为单向通行的,一种为双向通行的,分别用1和2来标记。如果存在由村庄A到达村庄B的通路,那么我们认为可以从村庄A到达村庄B,记为(A,B)。当(A,B)和(B,A)同时满足时,我们认为A,B是绝对连通的,记为<A,B>。绝对连通区域是指一个村庄的集合,在这个集合中任意两个村庄X,Y都满足<X,Y>。现在你的任务是,找出最大的绝对连通区域,并将这个绝对连通区域的村庄按编号依次输出。若存在两个最大的,输出字典序最小的,比如当存在1,3,4和2,5,6这两个最大连通区域时,输出的是1,3,4。
输入格式:
第1行:两个正整数N,M
第2…M+1行:每行三个正整数a,b,t, t = 1表示存在从村庄a到b的单向道路,t = 2表示村庄a,b之间存在双向通行的道路。保证每条道路只出现一次。(N <= 5000且M <= 50000)
输出格式:
第1行: 1个整数,表示最大的绝对连通区域包含的村庄个数。
第2行:若干个整数,依次输出最大的绝对连通区域所包含的村庄编号。

裸的求最大强连通分量的题(最适合我这种菜鸡练手了
代码:

#include <iostream>
#include <cmath>
#include <algorithm>
#include <stack>
#define LL long long
#define _for(i,j,k) for(int i=j;i<=k;i++)
#define for_(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
const int maxn = 5e3+5;
const int maxm = 5e4+5;
int n,m,cnt,head[maxn],net[maxm],e[maxm];
int vis[maxn],dfn[maxn],low[maxn],tar[maxn],ma,mark,ind,mi,mar;
stack<int> s;
void add_edge(int u,int v){//邻接表存图	
	e[++cnt]=v;	
	net[cnt]=head[u];
	head[u]=cnt;
}
void tarjan(int u){
	dfn[u]=low[u]=++ind;
	s.push(u);
	vis[u]=1;
	for(int i=head[u];i;i=net[i]){
		if(!dfn[e[i]]) tarjan(e[i]);
		if(vis[e[i]]) low[u]=min(low[u],low[e[i]]);	
	}
	if(low[u]==dfn[u]){
		mark++;//mark强连通分量编号
		int tcnt=0;//当前强连通分量包含的点数
		do{
			tar[s.top()]=mark;//tar[i]代表i是编号为tar[i]的强连通分量的点
			vis[s.top()]=0;
			s.pop();
			tcnt++;
		}while(vis[u]);
		if(tcnt>ma||(tcnt==ma&&u<mi)){
			ma=tcnt;//最大强连通分量包含的点数
			mi=u;//最大强连通分量包含的点中最小的点
			mar=mark;//最大强连通分量的编号
		}
	}
}
int main(){
	cin>>n>>m;
	int u,v,t;
	_for(i,1,m){
		cin>>u>>v>>t;
		add_edge(u,v);
		if(t==2) add_edge(v,u);
	}
	mi=n;
	_for(i,1,n){
		if(!dfn[i]) tarjan(i);
	}
	int kase=0;
	cout<<ma<<"\n";
	_for(i,1,n){
		if(tar[i]==mar){
			if(kase) cout<<" ";else kase++;//控制输出格式
			cout<<i;
		}
	}
	return 0;
}

二、割点、割边(桥)

割点: 对于一个连通的无向图,删去某一个点(连同与改点相连的边)后,图变得不连通了,该点就为一个割点。

方法是使用tarjan算法:
dfn[u]记录点u是第几个遍历到的,low[u]记录点u所在回路中可以到达的点中最小的时间戳。

假设dfs到u时,u存在一条边可以到达v,而v能到达的最早的点的时间戳比u的时间戳要大或等于(即low[v]>=dfn[u]),那么删去后,就存在包括v在内的一个联通块与u点前面一部分不联通,整个图就不联通了。
但是存在一个特殊情况:u的前面没有点,也就是说u是第一个遍历到的。这种情况下,如果存在两个以上的点满足low[v]>=dfn[u](v是与u相连的点),那么删去u以后图是不联通的。

具体步骤:

isc[maxn];//标记点是否为割点
cnt=0;//记录根结点分支数
tarjan(u,rt){//当遍历到u点时,rt点前联通块的根(最新遍历的点)
    dfn[u]=low[u]=++ind;//先初始化u点的dfn和low都等于当前的时间戳
    for(v:Edge(u)){//搜索u可以到达的所有点v
        if(!dfn[v]){
            tarjan(v,rt);//如果v点还没有被遍历过,遍历v点
            low[u]=min(low[u],low[v]);//更新low[u]的值
            if(u==rt) cnt++;
            if(low[v]>=dfn[u]&&u!=rt) isc[u]=1;//u是割点
        }
        low[u]=min(low[u],dfn[v]);//更新low[u]
    }
    if(u==rt&&cnt>=2) isc[u]=1;//根结点的特判
}

割边(桥): 对于一个连通的无向图,删去某一条边后,图变得不连通了,该点就为一个割点。

同样用的tarjan算法,套用上面割点的模板,把 low[v]>=dfn[u] 条件判断的等号去掉(low[v]>dfn[u]),记录边的编号就可以了。(如果用的链表形式的邻接表那么记录边的编号就灰常方便了)

三、双连通分量

点双连通: 若一个无向图中的去掉任意一个节点都不会改变此图的连通性,即不存在割点,则称作点双连通图。

边双连通: 若一个无向图中的去掉任意一条边都不会改变此图的连通性,即不存在割边(桥),则称作边双连通图。

点(边)连通图、点(边)连通分量和上面的强连通图、强连通分量类似。

TBC…

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值