区队A 强连通分量
DFS 生成树

首先,以这个有向图为例,我们可以发现其中有4类的边:
\[树边(tree edge):示意图中以黑色边表示,每次搜索找到一个还没有访问过的结点的时候就形成了一条树边.所有相邻的树边组成 DFS 生成树. \\
反祖边(back edge):也称回边,示意图中以红色边表示(即
7 \rightarrow 1),指在搜索过程中,从某个结点指向其祖先结点的非树边. \\
前向边(forward edge):示意图中以绿色边表示(即
3 \rightarrow 6),指在搜索过程中,从某个结点指向其子树中后代结点的非树边. \\
横叉边(cross edge):示意图中以蓝色边表示(即
9 \rightarrow 7),指在搜索过程中,从某个结点指向非祖先、非后代且已访问的结点的边,即不属于上述三类的边.
\]
那么考虑这个DFS生成树与强连通分量之间的关系
如果节点\(u\)是某个强连通分量在搜索树中遇到的第一个节点,那么这个强连通分量的剩余节点肯定是在搜索树中以\(u\)为根的子树中
Tarjan 算法
在Tarjan 算法中,我们需要对图进行\(DFS\),我们把每个联通分量看作是搜索树中的一颗子树,并在搜索过程中维护一个栈,每次都把未处理的\(node\)放入栈中
在Tarjan算法中,我们需要维护这几个变量
\[dfn:dfs过程中的序列,即dfs序\\
low:每个节点中可以回溯到的最早在栈中的node
\]
当然,我们现在可以发现一些结论:
- 一个节点的子树内的dfn都大于该节点的dfn
- 从根开始的一条路径上的dfn严格递增,low严格非降
Code
void tarjan(int x)
{
dfn[x] = low[x] = ++cnt;
sta[++top] = x;
in_sta[x]= 1;
for(int i = 0 ; i < e1[x].size(); i++)
{
int y = e1[x][i];
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x],low[y]);
}
else if(in_sta[y]) //返祖边
{
low[x] = min(low[x],dfn[y]);
}
}
if(dfn[x] == low[x])
{
++len;
do
{
belong[sta[top]] = len;
in_sta[sta[top]] = 0;
}while(sta[top--] != x);
}
}
B3609 [图论与代数结构 701] 强连通分量
思路
显而易见的,题目就要求你求出所有的强联通分量,而且是在有向图,那么其实就是找环,对于每个点,可以将其编号后处理
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e4+10;
int top, cnt, len;
bool in_sta[N],flag[N];
int dfn[N],low[N],belong[N],sta[N];
vector<int> e[N],scc[N];
void tarjan(int x)
{
dfn[x] = low[x] = ++cnt;
sta[++top] = x;
in_sta[x]= 1;
for(int i = 0 ; i < e[x].size(); i++)
{
int y = e[x][i];
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x],low[y]);
}
else if(in_sta[y]) //返祖边
{
low[x] = min(low[x],dfn[y]);
}
}
if(dfn[x] == low[x])
{
++len;
do
{
belong[sta[top]] = len;
scc[len].push_back(sta[top]);
in_sta[sta[top]] = 0;
}while(sta[top--] != x);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n,m;
cin>>n>>m;
for(int i = 1;i<=m;i++)
{
int u,v;
cin>>u>>v;
e[u].push_back(v);
}
memset(in_sta,0,sizeof(in_sta));
top = cnt = len = 0;
for(int i = 1;i <= n ; i ++)
{
if(dfn[i] == 0)
{
tarjan(i);
}
}
for(int i = 1;i <= len; i ++)
{
sort(scc[i].begin(),scc[i].end());
}
cout<<len<<'\n';
memset(flag,0,sizeof(flag));
for(int i = 1;i <= n;i++)
{
if(flag[i]) continue;
for(int j = 0 ; j < scc[belong[i]].size(); j++)
{
int x = scc[belong[i]][j];
flag[x] = 1;
cout<<x << ' ';
}
cout<<'\n';
}
return 0;
}
P3387 【模板】缩点 / 强连通分量
思路
首先,这里说了是有向图,并且重复经过的点全职只计算一次,那么考虑如果是环的情况,一个环就可以看作一个点上值为sum,那么就可以运用\(Tarjan\)进行缩点.
其次,我们需要求的是边权和最大,其实就是最长路,对于最长路,我们可以使用上节课的\(topo\)去求
Code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
int top, cnt, len;
bool in_sta[N];
int dfn[N],low[N],belong[N],sta[N];
int a[N],point[N],in_deg[N],que[N],ans[N];
pair<int,int> g[N];
vector<int> e1[N],scc[N],e2[N];
void tarjan(int x)
{
dfn[x] = low[x] = ++cnt;
sta[++top] = x;
in_sta[x]= 1;
for(int i = 0 ; i < e1[x].size(); i++)
{
int y = e1[x][i];
if(!dfn[y])
{
tarjan(y);
low[x] = min(low[x],low[y]);
}
else if(in_sta[y]) //返祖边
{
low[x] = min(low[x],dfn[y]);
}
}
if(dfn[x] == low[x])
{
++len;
do
{
belong[sta[top]] = len;
// scc[len].push_back(sta[top]);
in_sta[sta[top]] = 0;
}while(sta[top--] != x);
}
}
void topo()
{
copy(point+1,point+len+1,ans+1);
int front = 1,tail = 0;
for(int i = 1;i <= len; i ++)
{
if(in_deg[i]==0)
{
que[++tail] = i;
}
}
while(front <= tail)
{
int u = que[front++];
for(int i = 0 ; i < e2[u].size(); i ++)
{
int v = e2[u][i];
--in_deg[v];
ans[v] = max(ans[v],ans[u] + point[v]);
if(in_deg[v] == 0)
{
que[++tail] = v;
}
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
int n,m;
cin>>n>>m;
for(int i = 1;i<=n;i++)
{
cin>>a[i];
}
for(int i = 1;i<=m;i++)
{
int u,v;
cin>>u>>v;
g[i] = {u,v};
e1[u].push_back(v);
}
memset(dfn,0,sizeof(dfn));
memset(in_sta,0,sizeof(in_sta));
top = cnt = len = 0;
for(int i = 1;i <= n ; i ++)
{
if(dfn[i] == 0)
{
tarjan(i);
}
}
memset(point,0,sizeof(point));
memset(in_deg,0,sizeof(in_deg));
// cout<<len<<'\n';
// memset(flag,0,sizeof(flag));
for(int i= 1;i <= n;i++)
{
point[belong[i]] += a[i];
}
for(int i = 1;i<=m;i++)
{
int u = belong[g[i].first],v = belong[g[i].second];
if(u == v)
{
continue;
}
e2[u].push_back(v);
++in_deg[v];
}
topo();
int maxx = 0;
for(int i = 1;i<=len;i++)
{
maxx = max(maxx,ans[i]);
}
cout<<maxx;
return 0;
}
接下来的题目的概括
其实可以不难发现,我们对于一个环可以先计算出其奉献后去缩点,如果是有向图的话,我们就相当于是消掉了环,即转换为了\(DAG\),那么就可以使用\(topo\)求解
P.S:\(topo\)的算法复杂度是:\(O(n)\),而\(spfa\)的在最恶劣情况下会是\(Bellman-Ford\)的\(O(n \cdot m)\),所以可以转换

浙公网安备 33010602011771号