基础介绍
连通分量:对于分量中任意两点两点u,v;必然可以从u走到v,且从v走到u。
强连通分量:极大连通分量。
将任意一个有向图通过缩点的方式转化为有向无环图(拓扑图)(DAG)
好处:求最短路最长路可以递推来求解
如何求强连通分量
在DFS过程中
1.树枝边(x,y)
2.前向边(x,y)
3.后向边(x,y)
4.横插边(x,y)
判断某个点是否在某个强连通分量(scc)中?
情况1 存在一条后向边走到祖先节点
情况2 存在一条横叉边,横插边再走到祖先节点
Tarjan 求 scc
时间戳:
对于每个点定义两个时间戳
dfn[u]遍历到u的时间
low[u]从u开始走,所能遍历到的最小的时间戳
求每个强连通分量最高点
dfn[u] == low[u]
代码模板
void tarjan(int u)
{
dfs[u] = low[u] = ++ timestamp;
stk[++ top] = u,in_stk[u] = true;//栈
for(int i = h[u];~i;i=ne[i])
{
int j = e[i];
if(!dfs[j])
{
tarjan(j);
low[u] = min(low[u],low[j]);
}
else if(in_stk[j])
low[u] = min(low[u],low[j]);
}
if(dfn[u] == low[u])
{
int y;
++ scc_cnt;
do
{
y = stk[top--];
in_stk[y] = false; //出栈标记
id[y] = scc_cnt;
}while(y!=u);
}
}
//stk 当前还没有遍历完成强连通分量的所有点
题单
受欢迎的牛
题目传送门_受欢迎的牛:https://www.luogu.com.cn/problem/P2341
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;;
const int N = 1e5 + 10,M = 5e5 + 10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
int id[N],scc_cnt,Size[N];
int dout[N];
bool in_stk[N];
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ timestamp;
stk[++ top] = u;
in_stk[u] = true;
for(int i = h[u];~i;i=ne[i])
{
int j = e[i];
if(!dfn[j])
{
tarjan(j);
low[u] = min(low[u],low[j]);
}
else if(in_stk[j]) low[u] = min(low[u],dfn[j]);
}
if(dfn[u] == low[u])
{
++ scc_cnt;
int y;
do {
y = stk[top --];
in_stk[y] = false;
id[y] = scc_cnt;
Size[scc_cnt] ++ ;
}while(y != u);
}
}
int main()
{
cin >> n >> m;
memset(h,-1,sizeof h);
while(m --)
{
int a,b;
cin >> a >> b;
add(a,b);
}
for(int i = 1;i <= n;i ++)
if(!dfn[i]) tarjan(i);
for(int i = 1;i <= n;i ++)
for(int j = h[i];~j;j = ne[j])
{
int k = e[j];
int a = id[i],b = id[k];
if(a != b) dout[a] ++;
}
int zeros = 0;
int sum = 0;
for(int i = 1;i <= scc_cnt;i ++)
if(!dout[i])
{
zeros ++;
sum += Size[i];
if(zeros > 1)
{
sum = 0;
break;
}
}
cout << sum << endl;
return 0;
}
间谍网络(70分代码)
题目传送门_间谍网络:https://www.luogu.com.cn/problem/P1262
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;;
const int N = 1e5 + 10,M = 5e5 + 10;
const int INF = 0x3f3f3f3f;
int n,r;
int p;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int stk[N],top;
int id[N],scc_cnt;
bool is_ok[N];
int scc_money[N];
int min_id[N];
int din[N];
bool in_stk[N];
int buy[N];
int list[N];
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++ timestamp;
stk[++ top] = u;
in_stk[u] = true;
for(int i = h[u];~i;i=ne[i])
{
int j = e[i];
if(!dfn[j])
{
tarjan(j);
low[u] = min(low[u],low[j]);
}
else if(in_stk[j]) low[u] = min(low[u],dfn[j]);
}
if(dfn[u] == low[u])
{
++ scc_cnt;
int y;
do {
y = stk[top --];
in_stk[y] = false;
id[y] = scc_cnt;
if(buy[y]) is_ok[scc_cnt]= true;
scc_money[scc_cnt] = min(buy[y],scc_money[scc_cnt]);
min_id[scc_cnt] = min(min_id[scc_cnt],y);
}while(y != u);
}
}
int main()
{
memset(is_ok,false,sizeof is_ok);
memset(scc_money,INF,sizeof scc_money);
memset(min_id,INF,sizeof min_id);
cin >> n;
cin >> p;
for(int i = 0;i < p;i ++)
{
cin >> list[i];
cin >> buy[list[i]];
}
cin >> r;
memset(h,-1,sizeof h);
while(r --)
{
int a,b;
cin >> a >> b;
add(a,b);
}
for(int i = 1;i <= n;i ++)
if(!dfn[i]) tarjan(i);
for(int i = 1;i <= n;i ++)
for(int j = h[i];~j;j = ne[j])
{
int k = e[j];
int a = id[i],b = id[k];
if(a!=b) din[b]++;
}
bool flag = true;
for(int i = 1;i <= scc_cnt;i++)
if(!din[i])
flag *= is_ok[i];
if(flag)
{
long long sum = 0;
for(int i = 1;i <= scc_cnt;i ++)
if(!din[i])
sum += scc_money[i];
cout << "YES" << endl;
cout << sum << endl;
}
else
{
int minid = 1e9;
for(int i = 1;i <= scc_cnt;i ++)
if((!din[i])&&(!is_ok[i]))
minid = min(min_id[i],minid);
cout << "NO" << endl;
cout <<minid << endl;
}
return 0;
}