【模板】 tarjan 求强连通分量、割点、割边、缩点

强连通 割点 割边 视频:https://www.bilibili.com/video/av41752079/?p=1 (细节讲的很清楚了)

求强连通、缩点而言是有向图

求割点、割边而言是无向图

解释看代码


求强连通块:

习题:https://www.luogu.org/problemnew/show/P2921

代码:

#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 1e5+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;

ll n,dfn[maxn],low[maxn]; //inf表示第几个访问该点,low表示当前点能返回的最早时间戳
vector<ll>G[maxn];  //存图 
stack<ll>s;   
bool instack[maxn];  //标记是否在栈中 
ll timing;  //时间戳 
ll colorcnt;   //强连通分量数量 
ll color[maxn];  //第i个点属于哪个强连通分量 或者染成了哪种颜色 
ll colornum[maxn]; //该强连通分量里有多少个点 
ll ans[maxn];

void tarjan(ll now)
{	
	timing++;
	dfn[now] = low[now] = timing;
	s.push(now);
	instack[now] = true;
	
	for(ll i = 0; i < G[now].size(); i++)
	{
		ll to = G[now][i];
		
		//cout << to << endl;
		
		if(dfn[to] == 0)   //如果未访问
		{	
			tarjan(to);
			low[now] = min(low[now],low[to]);
		}
		else if(instack[to] == true)   //如果访问过且在栈中(属于同一强连通分量)
		    low[now] = min(low[now],dfn[to]);
			
			
	} 
	
	if(low[now] == dfn[now])  //找到该连通分量的初始点
	{
		colorcnt++;   
		while( s.top() != now )
		{
			ll tmp = s.top();
			s.pop();
			
			instack[tmp] = false;  //不要忘了清楚标记
			color[tmp] = colorcnt;
			colornum[colorcnt]++; 
		}
		
		ll tmp = s.top();  //处理初始点
		s.pop();
		
		color[tmp] = colorcnt;
		colornum[ colorcnt ]++;
		instack[tmp] = false;
	}
	
	return ;	
}

ll slove(ll now)
{
	if(now == G[now][0])
		return 1;
	
	if(ans[ G[now][0] ] != 0)
		return 1+ans[ G[now][0] ];
	else
		return 1+slove( G[now][0]  );
}

int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0),cout.tie(0);
	
	cin >> n;
		
	for(ll i = 1; i <= n; i++)
	{
		ll y;
		cin >> y;
		G[i].push_back(y);
	}
	
	
	for(ll i = 1; i <= n; i++)
	{
		if(dfn[i] == 0)
			tarjan(i);
	}
	
	/*for(ll i = 1; i <= n; i++)
	{
		cout << dfn[i] << " " << low[i] << endl;
	}
	
	cout << endl;*/
	
	for(ll i = 1; i <= n; i++)
	{
		if(colornum[ color[i] ] > 1)
			ans[i] = colornum[ color[i] ];
	}
	
	for(ll i = 1; i <= n; i++)
	{
		if(ans[i] == 0)
		{
			ans[i] = slove(i);
		}
	}
	
	for(ll i = 1; i <= n; i++)
	{
		cout << ans[i] << endl;
	}

	return 0;
}
/*
6 8
1 2
2 3
3 6
1 4
4 5
5 1
5 6
2 5
*/

求割点:

对于根节点的判断标准是有两颗不连通的子树就是割点

对于普通节点的判断标准是 low[to] >= dfn[now]  (也不能是根节点)

习题:https://www.luogu.org/problemnew/show/P3388

#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 2e4+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;

ll n,m,dfn[maxn],low[maxn],color[maxn],colornum[maxn],timing,colorcnt;
vector<ll>G[maxn];
set<ll>ans;

void tarjan(ll now,ll rt)   //rt是传进来的根节点,,固定不变,传下去
{
    ll child = 0;  //判断根节点是否有左右子树
    timing++;
    dfn[now] = low[now] = timing;
    
    for(ll i = 0; i < G[now].size(); i++)
    {
        ll to = G[now][i];
        if(dfn[to] == 0)
        {
            child++;
            tarjan(to,rt);
            low[now] = min(low[now],low[to]);
            
            if(low[to] >= dfn[now] && now != rt)  //对于普通节点的判断
            {
                ans.insert(now);
            }
            
        }
        
        low[now] = min(low[now],dfn[to]);  //更新自身
        
            
    }
    
    if(now == rt && child >= 2)  //对于根节点判断
    {
        ans.insert(now);
    }
    
    return ;
}

int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0),cout.tie(0);
    
    cin >> n >> m;
    
    for(ll i = 1; i <= m; i++)
    {
        ll x,y;
        cin >> x >> y;
        G[x].push_back(y);
        G[y].push_back(x);
    }
    
    for(ll i = 1; i <= n; i++)
    {
        if(dfn[i] == 0)
            tarjan(i,i);
    }
    
    //cout << colorcnt << endl;
    
    cout << ans.size() << endl;
    
    set<ll>::iterator it = ans.begin(); 
    
    while(it != ans.end())
    {
        cout << *it << " ";
        it++;
    } 
    cout << endl;
    
    return 0;
}

求割边:搜索时带着前驱节点,在自己不是自己父亲时更新

 low[to] > dfn[now]  是没有等于号的,视频里有讲解为什么不能有等于号 

习题:https://ac.nowcoder.com/acm/contest/392/I

#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 1e5+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;

ll n,m,ans,timing;
vector<ll>G[maxn];
ll dfn[maxn],low[maxn];

void tarjan(ll now,ll pre)
{
	timing++;
	low[now] = dfn[now] = timing;
	
	for(ll i = 0; i < G[now].size(); i++)
	{
		ll to = G[now][i];
		
		if(dfn[to] == 0)
		{
			tarjan(to,now);
			low[now] = min(low[now],low[to]);
			
			if(low[to] > dfn[now])
			{
				ans++;  //to到now 是一条割边 
			}
			
		}
		else if(to != pre)  //自己不是自己父亲时更新
		{
			low[now] = min(low[now],dfn[to]);
		}
		
	}
	
}

int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0),cout.tie(0);
	
	cin >> n >> m;
	
	for(ll i = 1; i <= m; i++)
	{
		ll x,y;
		cin >> x >> y;
		G[x].push_back(y);
		G[y].push_back(x);
	}	
	
	for(ll i = 1; i <= n; i++)
	{
		if(dfn[i] == 0)
		{
			tarjan(i,i);
		}
	}
	
	cout << m - ans << endl;
	
	return 0;
}




缩点:缩点与求强连通块类似

习题:http://poj.org/problem?id=2186

#include <iostream>
#include <cstring>
#include <iomanip>
#include <algorithm>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <map>
#include <cmath>
#include <cstdio>
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const ll maxn = 1e4+100;
const ll mod = 998244353;
const ld pi = acos(-1.0);
const ll inf = 1e18;

ll n,m,timing,ans;
vector<ll>G[maxn];
ll dfn[maxn],low[maxn];
ll color[maxn];
ll colornum[maxn];
ll colorcnt;
ll du[maxn];  //表示第i个强连通分量有几个出边 
stack<ll>s;
bool instack[maxn];

void tarjan(ll now)
{
	timing++;
	s.push(now);
	instack[now] = true;
	low[now] = dfn[now] = timing;
	
	for(ll i = 0; i < G[now].size(); i++)
	{
		ll to = G[now][i];
		
		if(dfn[to] == 0)
		{
			tarjan(to);
			low[now] = min(low[now],low[to]);
		}
		else if(instack[to] == true)
		{
			low[now] = min(low[now],dfn[to]);
		}
		
	}
	
	if(dfn[now] == low[now])
	{
		
		ll tmp;
		colorcnt++;
		while(s.top() != now)
		{
			tmp = s.top();
			s.pop();
			
			color[tmp] = colorcnt;
			colornum[colorcnt]++;
			instack[tmp] = false;
		}
		
		tmp = s.top();
		s.pop();
		
		color[tmp] = colorcnt;
		colornum[colorcnt]++;
		instack[tmp] = false;
			
	}
	
	return ;
}

int main()
{
    ios::sync_with_stdio(false);
    //cin.tie(0),cout.tie(0);
	
	cin >> n >> m;
	
	for(ll i = 1; i <= m; i++)
	{
		ll x,y;
		cin >> x >> y;
		G[x].push_back(y);
	}	
	
	for(ll i = 1; i <= n; i++)
	{
		if(dfn[i] == 0)
		{
			tarjan(i);
		}
	}
	
	//cout << colorcnt << endl;
	
	for(ll i = 1; i <= n; i++)     //进行缩点  
	{
		for(ll j = 0; j < G[i].size(); j++)  
		{
			ll to = G[i][j];
			if(color[i] != color[to])
			{
				du[ color[i] ]++;
			}
		}
		
	}
	
	ll num = 0,ans = 0; //num出度为0的点 
	for(ll i = 1; i <= colorcnt; i++)
	{
		if(du[i] == 0)
		{
			num++;
			ans = colornum[i];
		}	
	}
	
	if(num >= 2 || num == 0)
	{
		cout << 0 << endl;
	}
	else if(num == 1)
	{
		cout << ans << endl;
	}
	
	
	
	return 0;
}



猜你喜欢

转载自blog.csdn.net/Whyckck/article/details/88527899
今日推荐