HELLO WORLD--一起加油(🍺)!|

kingwzun

园龄:3年6个月粉丝:111关注:0

有向图的强连通分量 scc

基本概念:

  1. 树枝边(x,y):x是y的父亲

  2. 前向边(x,y):x是y的祖先结点

  3. 后向边(x,y):y是x的祖先结点

  4. 横叉边(x,y):在对有向图进行dfs遍历时,x是已经搜过的图的分支(不是前向边),现在在搜的点是y,y到x的有向边是横叉边

image

连通分量作用

  1. 通过缩点,将图变成有向无环图

缩点步骤:

for i=1;i<=n;i++
for i的所有邻点j
if i和j不在同一个scc中:
加一条新边id[i]→id[j]

tarjin算法求强连通分量

原文:

引入时间戳的概念:在dfs遍历的过程中,按照每个结点第一次被访问的时间顺序,依次给予图中N个结点1~N的整数标记,该标记被称为时间戳,记为dfn[x].

对每个点定义两个时间戳:

  1. dfn[u] 表示遍历到u的时间戳;
  2. low[u] 表示从u开始走,所能遍历到的最小时间戳是什么。

我们在求强连通分量的时候,求的是每个强连通分量最上面的那个点,也就是最高点。
若u是所在的强连通分量的最高点 等价于 dfn[u]== low[u],
因为low[u]表示的是从u开始能够遍历到的最小的时间戳,若正好等于自己的时间戳,则就是说明u是最高点。

tarjan模板

背模板的思路

  1. 加时间戳;
  2. 放入栈中,做好标记;
  3. 遍历邻点

1)如果没遍历过,tarjan一遍,用low[j]更新最小值low
2) 如果在栈中,用dfn[j]更新最小值low
4.找到最高点
1)scc个数++
2)do-while循环:
从栈中取出每个元素;标志为出栈;
对元素做好属于哪个scc;该scc中点的数量++

具体模板代码

// tarjan 算法求强连通分量
// 时间复杂度O(n+ m)
void tarjan(int u){
// 初始化自己的时间戳
dfn[u] = low[u] = ++ timestamp;
//将该点放入栈中
stk[++ top] = u, in_stk[u] = true;
// 遍历和u连通的点
for(int i = h[u]; ~i; i = ne[i]){
int j = e[i];
if(!dfn[j]){
tarjan(j);
// 更新u所能遍历到的时间戳的最小值
low[u] = min(low[u], low[j]);
}
// 如果当前点在栈中
//注意栈中存的可能是树中几个不同分支的点,因为有横叉边存在
// 栈中存的所有点,是还没搜完的点,同时都不是强连通分量的最高点
// 这里表示当前强连通分量还没有遍历完,即栈中有值
else if(in_stk[j])
//更新一下u点所能到的最小的时间戳
//此时j要么是u的祖先,要么是横叉边的点,时间戳小于u
low[u] = min(low[u], dfn[j]);
}
// 找到该连通分量的最高点
if(dfn[u] == low[u]){
int y;
++ scc_cnt; // 强连通分量的个数++
do{// 取出来该连通分量的所有点
y = stk[top --];
in_stk[y] = false;
id[y] = scc_cnt; // 标记点属于哪个连通分量
size_scc[scc_cnt] ++;
} while(y != u);
}
}

题目

1174. 受欢迎的牛

思路:
先找到强连通分量,缩点。

  • 假如存在两及以上个出度=0的牛(强连通分量) 则必然有一头牛(强连通分量)不被所有牛欢迎。
    因为出度都为零的牛(强连通分量)之间一点不会喜欢。
  • 只有一个牛(强连通分量)的出度为0,则该强连通分量中的所有点都被其他强连通分量的牛欢迎
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 5e5 + 10;
int n, m;
vector<int> g[N];
int dfn[N], low[N], timestemp;
int stk[N], top;
bool in_stk[N];
int id[N], scc_cnt, scc_size[N];
int dout[N]; // 记录新图中每个点(也就是原图每个连通分量)的出度
void tarjan(int u)
{
dfn[u] = low[u] = ++timestemp;
stk[++top] = u, in_stk[u] = true;
for (int i = 0; i < g[u].size(); i++)
{
int k = g[u][i];
// cout<<k<<endl;
if (!dfn[k])
{
tarjan(k);
low[u] = min(low[u], low[k]);
}
else if (in_stk[k])
{
low[u] = min(low[u], dfn[k]);
}
}
if (low[u] == dfn[u])
{
int k;
scc_cnt++;
do
{
k = stk[top--];
scc_size[scc_cnt]++;
id[k] = scc_cnt;
} while (k != u);
}
}
void slove()
{
cin >> n >> m;
for (int i = 0; i < m; i++)
{
int a, b;
cin >> a >> b;
g[a].push_back(b);
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i])
{
tarjan(i);
}
}
for (int i = 1; i <= n; i++)
{
for (int j : g[i])
{
if (id[i] != id[j])
{
dout[id[i]]++;
}
}
}
// 和本题有关的部分:
// zeros是统计在新图中,出度为0的点的个数
// sum表示满足条件的点(最受欢迎的奶牛)的个数
int zeros = 0, sum = 0;
for (int i = 1; i <= scc_cnt; i++)
{
if (!dout[i])
{
zeros++;
sum += scc_size[i];
if (zeros > 1)
{
sum = 0;
break;
}
}
}
cout << sum << endl;
}
signed main()
{
slove();
return 0;
}

练习

J - Watch Where You Step

题目:Kattis - watchyourstep

原文
题意
给定有向图的邻接矩阵,现在需要给该图增加边,使得如果两点可达必直接可达,求需要加边的最大数量。

思路:
通过强连通分量缩点。

  1. 对每个联通块内部
    将其中的点全部连接起来需要n*(n-1)条边(建完全图)
  2. 对每个联通块之间
    将其全部链接起来需要v[i]×v[j]条边(v[i]为每个块中的点数),为避免重复建边,需要对每个联通块排序。

因此对整个图进行两次dfs

  1. 第一次 对原图跑dfs 用栈记录连通分量缩后的点访问顺序
  2. 第二次 对反向图根据出栈顺序跑dfs 获得每个联通块中的点的个数;
    最后对每个块按序处理即可。
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int, int> pii;
const int N = 1e6 + 10;
const int INF = 0x3f3f3f3f3f;
int n;
int a[N];
int cnt[N],st[N];
vector<int> g[N];
vector<int> gg[N];
int vis[N];
int vis2[N];
stack<int> stk;
vector<int> vec;
int sum_bian;
void dfs(int u){
vis[u]=1;
for(int v: g[u]){
if(!vis[v]) dfs(v);
}
stk.push(u);
}
int dfs2(int u){
vis2[u]=1;
int res=1;
for(int v:gg[u]){
if(!vis2[v]) res+=dfs2(v);
}
return res;
}
void slove()
{
int n;cin>>n;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
int t;cin>>t;
if(t)
sum_bian++, g[i].push_back(j),gg[j].push_back(i);
}
}
for(int i=0;i<n;i++){
if(!vis[i]) dfs(i);
}
while(stk.size()){
int k=stk.top();
stk.pop();
if(!vis2[k]) {
int cnt=dfs2(k);
vec.push_back(cnt);
}
}
int ans=0;
for(int i = 0; i < vec.size(); ++i) {
ans += vec[i] * (vec[i] - 1);
for(int j = i + 1; j < vec.size(); ++j) {
ans += vec[i] * vec[j];
}
}
cout<<ans-sum_bian<<endl;
}
signed main()
{
slove();
return 0;
}

本文作者:kingwzun

本文链接:https://www.cnblogs.com/kingwz/p/16465873.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   kingwzun  阅读(38)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起