Tarjan

Tarjan

1. DFS树(深度优先搜索树)

  • 上图右图是左图以1为起点进行DFS时产生的生成树。

  • 有向图的 DFS 生成树主要有 4 种边(不一定全部出现):

    1. 树边(tree edge):绿色边,每次搜索找到一个还没有访问过的结点白点)的时候就形成了一条树边。
    2. 返祖边(back edge):黄色边,也被叫做回边,即指向祖先结点(灰点)的边。
    3. 横叉边(cross edge):红色边,它主要是在搜索的时候遇到了一个已经访问过黑点dfn[u]>dfn[v])的结点,但是这个结点 并不是 当前结点的祖先时形成的。
    4. 前向边(forward edge):蓝色边,它是在搜索的时候遇到子树中的结点黑点dfn[u]<dfn[v])的时候形成的。
  • 无向图不存在横叉边和前向边。

2. Tarjan算法求强连通分量

  • 强连通分量(Strongly Connected Components),经常简写为:SCC,有向图中任意两点间可达,实际上形成一个环。

  • Tarjan基于对图的深度优先搜索,并对每个节点引入两个值:

    • dfn[u]:节点u的时间戳,记录点uDFS过程中第几个访问的节点。
    • low[u]:记录节点uu的子树不经过搜索树上的边(树边)能够到达的时间戳最小的节点。
    • 初始时,dfn[u]==low[u]
  • 对于每一条与u相连的边<u,v>

    • 若在搜索树上vu的子节点,即边<u,v>是树枝边,则更新low[u]= min(low[u], low[v])
    • <u,v>不是搜索树上的边(反向边),则更新low[u]= min(low[u], dfn[v])
  • 缩点

    • 在有向图中,我们经常需要把一个SCC缩成一个点,然后生成一个有向无环图(DAG),或把一个无向图缩点后变成一棵树,然后可以有很多优秀的性质进行解决。
    • 算法实现:
      1. 从图的某一点u开始,对图进行DFS(u),点维护dfn[u]值和low[u]值。
      2. DFS时先将u压入栈中,然后遍历邻接边,邻接边定点为v
        • <u,v>为树边:DFS(v),回溯时更新:low[u]=min(low[u],low[v])
        • <u,v>为返祖边:直接更新:low[u]=min(low[u],dfn[v])
      3. 节点u变黑,即其所有子树访问结束时,若dfn[u]==low[u]时,此时栈顶节点到节点u,为一个SCC
  • 例题:缩点(洛谷p3387)

    Description
    • 给定一个 n 个点 m 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
    • 允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
    Input
    • 第一行两个正整数 n,m
    • 第二行 n个整数,依次代表点权
    • 第三至 m+2 行,每行两个整数 u,v,表示一条 \(u\rightarrow v\) 的有向边。
    Output
    • 共一行,最大的点权之和。
    Sample Input
    2 2
    1 1
    1 2
    2 1
    
    Sample Output
    2
    
    Hint
    • 对于 \(100\%\) 的数据,\(1\le n \le 10^4\)\(1\le m \le 10^5\),点权$ \in [0,1000]$。

    分析:

    • 题目说可重复的经过同一个点和边,但权值只算一次,如果图是一个强连通图的话,显然每个点我们都能走一遍,答案就是所有点的点权和。

    • 我们先对图进行Tanjan缩点并维护每个点的权值和sum[],缩点后,在原图的基础上我们建出新的DAG图。

    • 在新的DAG图中,我们可以进行拓扑排序+dp,定义dp[i]表示以节点i为起点的最大点权和,转移方程:dp[i]=max(dp[j]+sum[i])ji的子节点。

    • Code

       #include <bits/stdc++.h>
      using namespace std;
      const int maxn = 1e4 + 5, maxm = 1e5 + 5;
      struct Edge{
      	int from, to, next;
      }e1[maxm], e2[maxm];
      int len1, len2, head1[maxn], head2[maxn];
      void Insert(int u, int v, Edge e[], int &len, int head[]){
      	e[++len].from = u;
      	e[len].to = v;
      	e[len].next = head[u];
      	head[u] = len;
      }
      int n, m, a1[maxn], a2[maxn];
      void Read(){
      	scanf("%d%d", &n, &m);
      	for(int i = 1; i <= n; ++i)
      		scanf("%d", &a1[i]);
      	for(int i = 1; i <= m; ++i){
      		int u, v; scanf("%d%d", &u, &v);
      		Insert(u, v, e1, len1, head1);
      	}
      }
      int dfn[maxn], low[maxn], belong[maxn], Time, cnt;
      bool vis[maxn];//vis[i]=1表示i节点在栈里
      int st[maxn], top;//数组模拟栈
      void Tarjan(int u){
      	dfn[u] = low[u] = ++Time;
      	st[++top] = u; vis[u] = 1;
      	for(int i = head1[u]; i; i = e1[i].next){
      		int v = e1[i].to;
      		if(!dfn[v]){
      			Tarjan(v);
      			low[u] = min(low[u], low[v]);
      		}//v不在栈是反向边
      		else if(vis[v]) low[u] = min(low[u], dfn[v]);
      	}
      	if(low[u] == dfn[u]){
      		cnt++;//增加一个新图节点
      		while(st[top + 1] != u){
      			int v = st[top--];
      			vis[v] = 0;
      			a2[cnt] += a1[v];
      			belong[v] = cnt;//记录原图节点v在新图中的节点编号
      		}
      	}
      }
      int dp[maxn];//dp[i]表示以节点i开始的路径的最大点权和
      int res = 0;//存储答案
      void dfs(int u){
      	if(dp[u]) return;//记忆化
      	dp[u] = a2[u];
      	for(int i = head2[u]; i; i = e2[i].next){
      		int v = e2[i].to;
      		dfs(v);
      		dp[u] = max(dp[u], dp[v] + a2[u]);
      		// res = max(res, dp[u]);
      	}
      	res = max(res, dp[u]);//不要写在上面,避免欧拉回路
      }
      /*
      3 4
      1 2 3
      1 3
      1 2
      2 1
      3 2
      */
      void Print(int a[]){
      	for(int i = 1; i <= n; ++i)
      		printf("%d ", a[i]);
      	printf("\n--------\n");
      }
      void sol(){
      	Read();
      	for(int i = 1; i <= n; ++i){
      		if(!dfn[i]) Tarjan(i);
      	}
      	for(int i = 1; i <= len1; ++i){
      		int u = belong[e1[i].from], v = belong[e1[i].to];
      		if(u != v) Insert(u, v, e2, len2, head2);
      	}
      	for(int i = 1; i <= cnt; ++i)
      		if(!dp[i])
      			dfs(i);
      	printf("%d\n", res);
      }
      int main(){
      	sol();
      	return 0;
      }
      
posted @ 2020-04-29 13:44  ♞老姚♘  阅读(963)  评论(0编辑  收藏  举报