一些概念

无向图:

连通图:在无向图中,任意两点都直接或间接连通,则称该图为连通图。(或者说:任意两点之间都存在可到达的路径)

连通分量: G的 最大连通子图 称为G的连通分量

有向图 (ps.区别在与“强”)

强连通图: 在有向图中,对于每一对顶点Vi,Vj都存在从Vi到Vj和从Vj到Vi的路径(任意两点之间都存在可到达对方的路径),则称该图为强连通图

强连通分量有向图G的 最大强连通子图 称为G的强连通分量

求强连通分量+有向图的压缩(缩点)

缩点即讲一个强连通分量中的点标记为同一类,看作一个大点包含所有其中点的信息
可以用Kosaraju但是,这里讲的是精妙的Tarjan算法
背景:dfs序
思想:做一遍DFS,用dfn[i]表示编号为i的节点在DFS过程中的访问序号(也可以叫做开始时间)用low[i]表示i节点及其下方节点所能到达的开始时间最早的节点的 开始时间 。初始时dfn[i]=low[i]
ps.大家一开始不要过于纠结low的意思,很多时候是根据需求和用途来的

important:

如果一个节点的low值等于dfn值,则说明其下方的节点不能走到其上方的节点,那么该节点u就是一个强连通分量在DFS搜索树中的根。

但是u的子孙节点未必就和u处于同一个强连通分量,怎样找出u的子孙中,哪些跟它位于同一强连通分量?
这里需要用到
栈的用途是保存搜过的结点并能及时删除,先上代码

int dfn[N],low[N],Time,SCC=0,Belong[N],In_du[N];
bool In_stack[N];
stack<int> st;
void Tarjan(int u) {
	dfn[u]=low[u]=++Time;
	st.push(u); In_stack[u]=true;
	for(int v=1;v<=n;v++) {
		if(mp[u][v]&&!dfn[v]) {
			Tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(mp[u][v]&&dfn[v]!=0&&In_stack[v]) {
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(low[u]==dfn[u]) {
		++SCC; int v;
		do {
			v=st.top(); st.pop();
			In_stack[v]=false; Belong[v]=SCC;	//belong 缩点 
		}while(v!=u);	//直到删除了最高点
	}
}

注意的是理解代码的In_stack[]标记,
因为如果你不可以反祖边指向了一个被遍历过且已经出栈的结点。(一个点一旦出了栈,它便有了belong)


缩点使图缩为DAG图
因此拓扑排序(图上的动态规划),在此前许会用缩点预处理

此处设一道基础例题:【APIO2009】抢掠计划
题意是:有向图中,有些点是酒吧,有的不是。每一个点都有它的价值,不过你只能抢一次并加上该价值。你从s出发到任意一个有酒吧的点结束。问您能抢到的最大价值是多少。
方程是:dp[v] = Max{ dp[u]+len[u][v]; }
为了保证无后效性:按照 Tarjan缩点,处理新图,拓扑排序中DAG动态规划/记忆化搜索 的顺序,来码题:

    #include<bits/stdc++.h>
    using namespace std;
    const int N=5e5+5;
    int head[N],to[N],tot,nxt[N],val[N],jb[N];
    int Head[N],To[N],Tot,Nxt[N],f[N];
    int Time,dfn[N],low[N],Belong[N],size[N],SCC,In[N];
    stack<int> st;
    queue<int> Q;
    bool Instack[N];
    void add_edge(int u,int v) {
    	tot++; nxt[tot]=head[u]; to[tot]=v; head[u]=tot;
    }
    void Add_edge(int u,int v) {
    	Tot++; Nxt[Tot]=Head[u]; To[Tot]=v; Head[u]=Tot;
    }
    void Tarjan(int u) {
    	dfn[u]=low[u]=++Time;
    	st.push(u); Instack[u]=true;
    	for(int i=head[u];i;i=nxt[i]) {
    		int v=to[i];
    		if(!dfn[v]) {
    			Tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(Instack[v]) {
    			low[u]=min(low[u],dfn[v]);
    		}
    	}
    	if(dfn[u]==low[u]) {
    		SCC++; int v;
    		do {
    			v=st.top(); st.pop(); Instack[v]=false; 
    			Belong[v]=SCC; size[SCC]+=val[v];
    		}while(v!=u);
    	}
    }

    int main() {
    	int n,m,s,p;
    	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++) scanf("%d",&val[i]);
    	scanf("%d%d",&s,&p);
    	for(int i=1;i<=p;i++) scanf("%d",&jb[i]);
    	Tarjan(s);		//这样被缩的点(有belong的点)都能被s到达 
    	for(int u=1;u<=n;u++) {
    		for(int j=head[u];j;j=nxt[j]) {
    			int v=to[j];
    			if(!Belong[u]||!Belong[v]) continue;
    			if(Belong[u]!=Belong[v]) {
    				Add_edge(Belong[u],Belong[v]); In[Belong[v]]++;
    			}
    		}
    	}
    	Q.push(Belong[s]);
    	for(int i=1;i<=SCC;i++) {
    		f[i]=size[i];
    	}
    	while(!Q.empty()) {
    		int u=Q.front(); Q.pop();
    		for(int j=Head[u];j;j=Nxt[j]) {
    			int v=To[j];
    			f[v]=max(f[v],f[u]+size[v]);
    			In[v]--; if(!In[v]) Q.push(v);
    		}
    	}
    	int ans=0;
    	for(int i=1;i<=p;i++) {
    		if(Belong[jb[i]]) {
    			ans=max(ans,f[Belong[jb[i]]]);
    		}
    	}
    	printf("%d",ans);
    	return 0;
    }

再讲一道有趣的强连通分量题目吧。
洛谷 P1407 稳定婚姻
题目大概讲了2n个点,其中有n对夫妻,又有m对情侣,请问能否在第i对夫妻离婚的情况下,所有人都能找到对象。
思路:将每对夫妻(女->男)建有向图,再将每对情侣按照(男->女)连边,【环的点数都为偶】 判断每队夫妻是否在同一个一个强连通分量上,这样它们不在一起时,才能错序排列以配偶。
因此,代码:

    #include<stdio.h>
    #include<bits/stdc++.h>
    using namespace std;
    const int N=1e5+5;
    int a[N],b[N];
    int num,tot,ng,nb,head[N],nxt[N],to[N],cnt;
    int Time,dfn[N],low[N],Belong[N],SCC;
    bool Instack[N];
    stack<int> st;
    void add_edge(int u,int v) {
    	tot++; nxt[tot]=head[u]; to[tot]=v; head[u]=tot;
    }
    void Tarjan(int u) {
    	dfn[u]=low[u]=++Time;
    	st.push(u); Instack[u]=true;
    	for(int i=head[u];i;i=nxt[i]) {
    		int v=to[i];
    		if(!dfn[v]) {
    			Tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(Instack[v]) {
    			low[u]=min(low[u],dfn[v]);
    		}
    	}
    	if(dfn[u]==low[u]) {
    		SCC++; int v;
    		do {
    			v=st.top(); st.pop(); Instack[v]=false; 
    			Belong[v]=SCC;
    		}while(v!=u);
    	}
    }
    map<string,int> mp;
    int main() {
    	int n,m;
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) {
    		string p,q;
    		cin>>p>>q;
    		mp[p]=++num;
    		mp[q]=++num;
    		a[i]=mp[p]; b[i]=mp[q];
    		add_edge(a[i],b[i]);
    	}
    	scanf("%d",&m);
    	for(int i=1;i<=m;i++) {
    		string p,q;
    		cin>>p>>q;
    		int u=mp[p],v=mp[q];
    		add_edge(v,u);
    	}
    	for(int i=1;i<=n*2;i++) {
    		if(!dfn[i]) {
    			Tarjan(i);
    		}
    	}
    	for(int i=1;i<=n;i++) {
    		if(Belong[a[i]]!=Belong[b[i]]) {
    			printf("Safe\n");
    		}
    		else printf("Unsafe\n");
    	}
    	return 0;
    }