有向图的强连通分量

前置

连通分量定义:在\(u\)\(v\)在一个强连通分量中,则存在\(u\)\(v\)的路径和\(v\)\(u\)的路径。

强连通分量scc:极大连通分量

作用:通过缩点将有向图转换为有向无环图DAG(拓扑图),将题目变得好做。

:树枝边、前向边、后向边、横叉边

判断一个点是否在某个scc中

  1. 存在一条后向边指向祖先节点。
  2. 先走到横叉边,横叉边再走到祖先节点。

注意:对于前向边不会成为判断形成回路的关键。

tarjan算法求强连通分量SCC

对于每个点定义两个时间戳\(dfn[u]\)表示遍历到u的时间,\(low[u]\)表示从\(u\)开始走所能遍历到的最小时间戳。

u是其所在的强连通分量的最高点,等价于\(dfn[u] == low[u]\)

连通分量编号递减的顺序一定是拓扑序。

受欢迎的牛

#include <bits/stdc++.h>
using namespace std;
const int N = 10005, M = 50005;
int n, m, head[N], cnt = -1, tot = 0, dfn[N], low[N];
int scc_cnt = 0, id[N], out[N], siz[N];
stack<int> stk;
bool in_stk[N];
struct edge
{
	int to, nxt;
}e[M];
void add_edge(int u, int v)
{
	e[++ cnt].to = v, e[cnt].nxt = head[u], head[u] = cnt;
	return ;
}

void tarjan(int x)
{
	dfn[x] = low[x] = ++ tot, stk.push(x), in_stk[x] = true;
	
	for (int i = head[x]; i != -1; i = e[i].nxt)
	{
		int y = e[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
		else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
	}
	if(low[x] == dfn[x])
	{
		int y = 0;
		scc_cnt ++;
		do
		{
			y = stk.top(), stk.pop();
			siz[scc_cnt] ++;
			id[y] = scc_cnt;
			in_stk[y] = false;
		}while(y != x);
	}
	return ;
} 
void solve()
{
//	printf("%d %d!\n", scc_cnt, siz[1]);
	for (int x = 1; x <= n; ++x)
	{
		for (int j = head[x]; j != - 1; j = e[j].nxt)
		{
			int y = e[j].to;
			if(id[x] != id[y]) out[id[x]] ++;
		}
	}
	int c = 0, ans = 0;
	for (int i = 1; i <= scc_cnt; ++ i)
	{
		if(!out[i]) c ++, ans = siz[i];
	}
	if(c == 1) printf("%d", ans);
	else printf("0");
	return ;
}
int main()
{
	memset(head, -1, sizeof(head));
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; ++ i)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		add_edge(u, v);
	}
	for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
	solve();
}

学校网络

#include <bits/stdc++.h>
using namespace std;
const int N = 105, M = N * N;
struct edge
{
	int to, nxt;
}e[M];
int head[N], cnt = -1; 
int n, dfn[N], low[N], timestamp = 0, scc_cnt = 0, id[N], out[N];
stack<int> stk;
bool in_stk[N]; 
void add_edge(int u, int v)
{
	e[++ cnt].to = v, e[cnt].nxt = head[u], head[u] = cnt;
	return ;
}
void tarjan(int x)
{
	low[x] = dfn[x] = ++ timestamp;
	stk.push(x), in_stk[x] = true;
	for (int i = head[x]; i != -1; i = e[i].nxt)
	{
		int y = e[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
		else if(in_stk[y]) low[x] = min(low[x], dfn[y]); 
	}
	if(low[x] == dfn[x])
	{
		int y;
		scc_cnt ++;
		do
		{
			y = stk.top(), stk.pop();
			id[y] = scc_cnt;
			in_stk[y] = false;
		}while(y != x);
	}
	return ;
}
int in[N];
void solve()
{
	for (int x = 1;  x <= n; ++x)
	{
		for (int i = head[x]; i != -1; i = e[i].nxt)
		{
			int y = e[i].to;
			if(id[x] != id[y]) in[id[y]] ++, out[id[x]] ++;
		}
	}
	int sum = 0, tot = 0;
	for (int i = 1; i <= scc_cnt; ++ i)
	{
		 if(!in[i]) sum ++; 
		 if(!out[i]) tot ++;
	}
	printf("%d\n", sum);
	if(scc_cnt != 1) printf("%d", max(tot, sum));
	else printf("0");
	return ;
}
int main()
{
	memset(head, -1, sizeof head);
	scanf("%d", &n);
	for (int i = 1; i <= n; ++ i)
	{
		int x;
		while(true)
		{
			scanf("%d", &x);
			if(x == 0) break;
			add_edge(i, x);
		}
	}
	for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
	solve();
	return 0;
}

最大半连通子图

#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 1000005;
int n, m, p, cnt = -1, timestamp = 0, scc_cnt = 0, low[N], dfn[N], f[N], g[N], head[N], siz[N], id[N]; 
int in[N];
stack<int> stk;
bool in_stk[N], vis[N];
struct edge
{
	int to, nxt;
}e[M];
void add_edge(int u, int v)
{
	e[++ cnt].nxt = head[u], e[cnt].to = v, head[u] = cnt;
	return ;
}
vector<int> G[N]; 
void tarjan(int x)
{
	low[x] = dfn[x] = ++ timestamp, stk.push(x), in_stk[x] = true;
	for (int i = head[x]; i != -1; i = e[i].nxt)
	{
		int y = e[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
		else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
	}
	if(low[x] == dfn[x])
	{
		int y;
		scc_cnt ++;
		do
		{
			y = stk.top(), stk.pop();
			in_stk[y] = false;
			siz[scc_cnt]++, id[y] = scc_cnt;
		}while(y != x);
	}
	return ;
}
void build()
{
	for (int x = 1; x <= n; ++ x)
	{
		for (int j = head[x]; j != -1; j = e[j].nxt)
		{
			int y = e[j].to;
			if(id[x] != id[y]) G[id[x]].push_back(id[y]), in[id[y]] ++ ;
		}
	}
	return ;
}
int main()
{
	scanf("%d %d %d", &n, &m, &p);
	for (int i = 0; i <= n; ++ i) head[i] = -1;
	while(m --)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		add_edge(u, v);
	}
	for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
	build();
	int ans = 0, sum = 0;
	for(int x = scc_cnt; x >= 1; -- x)
	{
		if(!in[x]) g[x] = 1;
		for (int j = 0; j < G[x].size(); ++ j)
		{
			int y = G[x][j];
			f[y] = max(f[y], f[x] + siz[x]);
		}
	}
	for (int x = scc_cnt; x >= 1; -- x)
	{
		ans = max(ans, f[x] + siz[x]);
		for (int j = 0; j < G[x].size(); ++ j)
		{
			int y = G[x][j];
			if(f[y] == f[x] + siz[x] && !vis[y]) vis[y] = 1, g[y] = (g[y] + g[x]) % p;
		}
		for (int j = 0; j < G[x].size(); ++ j) vis[G[x][j]] = 0;
	}
	for (int x = scc_cnt; x >= 1; -- x)
	{
		if(ans == f[x] + siz[x]) sum = (sum + g[x]) % p;
	}
	printf("%d\n%d", ans, sum);
	return 0;
}

银河

这个题可以不用差分s约束里的spfa来判断,是因为这个图具有一定的特殊性,所有的边的权值都是正数->能用强连通分量保证稳定的线性时间复杂度。

问题就转化成了在拓扑图上面找 到一个点的最长路,然后累加就可以了。

/*
T = 1 A == B
T = 2 A < B
T = 3 A >= B
T = 4 A > B
T = 5 A <= B
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 300005;
typedef long long ll;
ll ans = 0;
struct edge1
{
	int to, nxt, w;
}e[M];
struct edge2
{
	int v, w;
};
vector<edge2> G[N];
int n, m, cnt = -1, timestamp = 0, scc_cnt = 0, head[N];  
int low[N], dfn[N], id[N], in[N], siz[N];
stack<int> stk;
bool in_stk[N], vis[N];
void add_edge(int u, int v, int w)
{
	e[++ cnt].to = v, e[cnt].nxt = head[u], e[cnt].w = w, head[u] = cnt;
	return ;
}
void tarjan(int x)
{
	low[x] = dfn[x] = ++timestamp, in_stk[x] = true, stk.push(x);
	for (int i = head[x]; i != -1; i = e[i].nxt)
	{
		int y = e[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x] = min(low[x], low[y]);
		}
		else if(in_stk[y]) low[x] = min(low[x], dfn[y]);
	}
	if(low[x] == dfn[x])
	{
		int y;
		scc_cnt ++;
		do
		{
			y = stk.top(), stk.pop();
			id[y] = scc_cnt, siz[scc_cnt] ++;
			in_stk[y] = false;
		}while(y != x);
	}
	return ;
}
bool judge()
{
	bool pd = true;
	for (int x = 1; x <= n; ++ x)
	{
		for (int j = head[x]; j != - 1; j = e[j].nxt)
		{
			int y = e[j].to;
			if(id[x] == id[y] && e[j].w) {pd = false; break; }
			else if(id[x] != id[y]) G[id[x]].push_back((edge2){id[y], e[j].w}), in[id[y]] ++;
		}
	}
	return pd;
}
int dp[N];
int main()
{
	scanf("%d %d", &n, &m);
	for (int i = 0; i  <= n; ++ i) head[i] = -1;
	while(m --)
	{
		int T, u, v;
		scanf("%d %d %d", &T, &u, &v);
		if(T == 1) add_edge(u, v, 0), add_edge(v, u, 0);
		else if(T == 2) add_edge(u, v, 1);
		else if(T == 3) add_edge(v, u, 0);
		else if(T == 4) add_edge(v, u, 1);
		else if(T == 5) add_edge(u, v, 0);
	}
	for (int i = 1; i <= n; ++ i) if(!dfn[i]) tarjan(i);
	if(judge() == false)
	{
		printf("-1");
	}
	else
	{
		for (int i = 1; i <= scc_cnt; ++ i)
		{
			if(!in[i]) G[scc_cnt + 1].push_back((edge2){i, 1});
		}
		for (int x = scc_cnt + 1; x >= 1; -- x)
		{
			for (int j = 0; j < G[x].size(); ++ j)
			{
				int y = G[x][j].v, w = G[x][j].w;
				dp[y] = max(dp[y], dp[x] + w);
			}
		}
		for (int x = scc_cnt; x >= 1; -- x) ans = ans + (ll)dp[x] * siz[x];
		printf("%lld", ans);
	}
	return 0;
}
posted @ 2025-02-13 10:40  Helioca  阅读(87)  评论(0)    收藏  举报
Document