参考
基本概念:
强连通:有向图G中,如果某个结点v1有一条到v2的路径且v2有一条到v1的路径,则称这两个点强连通;
强连通图:若有向图G中任意两个点都强连通,则称G是强连通图;
强连通分量:有向图的极大强连通子图称为强连通分量,注意是极大而非最大,这包含了两个意思:1.有向图的强连通分量可以有多个;2.如果选取了某个子图G2且G2强连通,但在图G中还存在一个点v,使得v和G2中任意一个点强连通,那么G2不能称为强连通分量,只有当对于任意v∈G且v不属于G2,G2中存在一个点和v非强连通时,G2才能称为强连通分量;
tarjan算法基于dfs算法,利用栈的特性求解一个有向图的强连通分量,图中的顶点只会在一个强连通分量中出现,时间复杂度为O(V+E)。
其关键在于如何判断强连通分量的“根”,也就是在dfs的时候对于一个强连通分量首先访问到的结点。其实dfs的时候,假设当前点为u,那么会形成一棵以u为根的树;只是这棵树的某些节点和其父亲或祖先有联系;这个u就是我们所谓的“根”。
算法实现的过程中维护两个值,一个栈;
dfn[i]:代表i结点在dfs的时候的进入时间戳,也就是第几个被访问的
low[i]:作为点i在这颗树中的,最小的子树的根。。(懂不起)
栈:用于存储位于同一个强连通分量的结点;
实现过程:
1.选择一个未被dfs过的点开始dfs
2.给当前结点u的dfn[u]赋值,low[u]初始化为和dfn[u]相等;
3.把当前节点u压入栈中,并标记当前节点u处于栈中
4.遍历与当前的点u相连的点v
(1).如果v未被访问过,递归调用该算法,并在结束后更新low[u] = min(low[u],low[v]);
(2).如果v已被访问过且仍在栈中,直接更新low[u] = min(low[u],dfn[v]);
5.遍历结束后如果dfn[u] = low[u],则代表u是一个强连通分量的“根”,栈中位于u之后的点都和u处于同一个强连通分量;
举个例子:
有一说一,字太丑了,自己都看不下去,。。。
模板
void tarjan(int u,int p) {
dfn[u] = low[u] = ++index;
stk[top++] = u;
vis[u] = 1;
for(int i = head[u];~i; i = edge[i].nxt) {
int v = edge[i].v;
if(!dfn[v]) {
tarjan(v,u);
low[u] = min(low[u],low[v]);
}else if(vis[v]) {
low[u] = min(low[u],dfn[v]);
}
}
int tt;
if(dfn[u] == low[u]) {
num++; //用于保存强连通分量的个数
do {
tt = stk[--top];
bel[tt] = num;
vis[tt] = 0;
}while(tt != u);
}
}
//调用,如果原图不是联通图,那么就需要循环调用;
for(int i = 1;i <= n; ++i) {
if(!dfn[i])
tarjan(i,0);
}
由于强连通分量中各个点相互可达,有些时候可以当成一个点来处理,并且能够解决掉带环的问题,因为很多算法都需要无环这个条件;
例题:poj3160 (tarjan缩点+dp)
//#include<bits/stdc++.h>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
#include<set>
#define mod (10007)
#define middle (l+r)>>1
#define SIZE 1000000+5
#define lowbit(x) (x&(-x))
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long ll;
typedef long double ld;
const int inf_max = 0x3f3f3f;
const ll Linf = 9e18;
const int maxn = 3e4 + 10;
const long double E = 2.7182818;
const double eps=0.0001;
using namespace std;
inline int read()
{
int f=1,res=0;
char ch=getchar();
while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9') { res=res*10+ch-'0' ; ch=getchar(); }
return f*res;
}
struct EDG {
int v,nxt;
}edge[150000 + 10];
vector<pair<int,int> >s[maxn];
int n,m,hpy[maxn],bel[maxn],stk[maxn],head[maxn],dfn[maxn],vis[maxn],low[maxn],cnt,index,top,num,w[maxn],dp[maxn];
void Initial() {
cnt = index = top = 0,num = 1;
memset(head,-1,sizeof(head));
memset(vis,0,sizeof(vis));
memset(dfn,0,sizeof(dfn));
memset(dp,0,sizeof(dp));
memset(w,0,sizeof(w));
}
void ade(int u,int v) {
edge[cnt].v = v;
edge[cnt].nxt = head[u];
head[u] = cnt++;
}
//tarjan缩点,从0开始存储
void tarjan(int u,int p) {
dfn[u] = low[u] = ++index;
stk[top++] = u;
vis[u] = 1;
for(int i = head[u];~i; i = edge[i].nxt) {
int v = edge[i].v;
if(!dfn[v]) {
tarjan(v,u);
low[u] = min(low[u],low[v]);
}else if(vis[v]) {
low[u] = min(low[u],dfn[v]);
}
}
int tt;
if(dfn[u] == low[u]) {
do {
tt = stk[--top];
bel[tt] = num;
if(hpy[tt] > 0) w[num] += hpy[tt];
vis[tt] = 0;
}while(tt != u);
num++;
}
}
void mk_newmp() {
for(int i = 0;i < num; ++i) s[i].clear();
for(int i = 1;i <= n; ++i) {
for(int j = head[i];~j; j = edge[j].nxt) {
int v = edge[j].v;
if(bel[i] != bel[v]) {
s[bel[i]].push_back(make_pair(bel[v],w[bel[v]]));
}
}
}
for(int i = 1;i < num; ++i) {
s[0].push_back(make_pair(i,w[i]));
}
}
int dfs(int u) {
if(dp[u] > 0) return dp[u];
for(int i = 0;i < s[u].size();i++) {
int v = s[u][i].first,val = s[u][i].second;
dp[u] = max(dp[u],dfs(v) + val);
}
return dp[u];
}
int main()
{
while(~scanf("%d%d",&n,&m)) {
Initial();
for(int i = 1;i <= n; ++i) hpy[i] = read();
for(int i = 1;i <= m; ++i) {
int u = read(),v = read();
u++,v++;
ade(u,v);
}
for(int i = 1;i <= n; ++i) {
if(!dfn[i])
tarjan(i,0);
}
mk_newmp();
cout<<dfs(0)<<endl;
}
return 0;
}