• 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 ightarrow v) 的有向边。
      Output
      • 共一行,最大的点权之和。
      Sample Input
      2 2
      1 1
      1 2
      2 1
      
      Sample Output
      2
      
      Hint
      • 对于 (100\%) 的数据,(1le n le 10^4)(1le 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>
        const int maxn=100000+5
        struct edge{
        	int from,to, next;
        }e[maxn << 1],g[maxn << 1];//e原图,g存储新图DAG 
        int lene,leng, heade[maxn],headg[maxn];//图e和g的边表 
        int Time, dfn[maxn], low[maxn], vis[maxn],s[maxn],top;//s是模拟栈,要全局 
        int dp[maxn], n, m, sum[maxn], a[maxn], tot, belong[maxn];
        void Inserte(int x, int y){ e[++lene].from=x;e[lene].to =y;e[lene].next=heade[x], heade[x] = lene; }//原图
        void Insertg(int x, int y){ g[++leng].from=x;g[leng].to =y;g[leng].next=headg[x], headg[x] = leng; }//新图
        void tarjan(int u){
        	dfn[u] = low[u] = ++Time;//初始化
        	vis[u] = 1;s[++top]=u;//标记并进栈
        	for (int i = heade[u]; i; i = e[i].next){
        		int v = e[i].to;
        		if (!dfn[v]) {//v为白点
        			tarjan(v);
        			low[u] = std::min(low[u], low[v]);//<u,v>为树枝子节点low值更新父节点low值
        		}//否则有可能是返祖边
        		else if (vis[v]) low[u] = std::min(low[u], dfn[v]);
        	}
        	if(dfn[u] == low[u]){//缩点
        		++tot;//记录新图节点数
        		while(s[top+1]!=u){//从栈顶到u的点缩成一个新点tot
        			int v=s[top];
        			belong[v]=tot;vis[v]=0;sum[tot]+=a[s[top--]];
        		}//belong表示强连通分量编号,vis表示是否在栈中,sum表示强连通分量权值和
        		
        	}
        }
        void dfs(int u){
        	if (dp[u]) return;
        	dp[u] = sum[u];
        	for (int i = headg[u]; i; i = g[i].next){
        		int v = g[i].to;
        		dfs(v);
        		dp[u] = std::max(dp[u], dp[v] + sum[u]);//这里是记忆化
        	}
        }
        int main(){
        	scanf("%d%d",&n,&m);
        	for (int i = 1; i <= n; ++i) scanf("%d",&a[i]);
        	for (int i = 1; i <= m; ++i){
        		int x,y;scanf("%d%d",&x,&y);
        		Inserte(x,y);
        	}
        	for (int i = 1; i <= n; ++i)
        		if (!dfn[i]) tarjan(i);
        	leng = 0;
        	memset(headg, 0, sizeof(headg));
        	for (int i = 1; i <= m; ++i){//遍历原始边建新图
        		int u=e[i].from,v=e[i].to;
        		if (belong[u] != belong[v]) //判断边的两个端点是否在同一个新点中
        			Insertg(belong[u], belong[v]);//不在就建一条边
        	}
        	int ans=0;		
        	for (int i = 1; i <= tot; ++i)
        		if (!dp[i]){
        			dfs(i);
        			ans = std::max(ans, dp[i]);
        		}
        	printf("%d
        ", ans);
        	return 0;
        }
        
  • 相关阅读:
    js_css_dl.dt实现列表展开、折叠效果
    property_自己编写一个读取Property文件的Util类
    HttpClient_002_中文乱码、HttpClient中文乱码透析、总结
    HttpClient_001_初步实现项目01的servlet,与项目02的servlet,之间数据访问
    jsp:中文乱码解决
    linux命令
    js 监测from表单中的input和select,时时监测,没有输入或选择信息报错,不允许提交数据
    数据库的那些事
    待参考
    layer.open多次触发,遮罩层覆盖content的解决办法
  • 原文地址:https://www.cnblogs.com/hbhszxyb/p/12801709.html
Copyright © 2020-2023  润新知