标签:lan lock ros 搜索 访问 题目 一点 strong 绿色
Tarjan
DFS
树(深度优先搜索树)上图右图是左图以1
为起点进行DFS
时产生的生成树。
有向图的 DFS
生成树主要有 4
种边(不一定全部出现):
tree edge
):绿色边,每次搜索找到一个还没有访问过的结点(白点)的时候就形成了一条树边。back edge
):黄色边,也被叫做回边,即指向祖先结点(灰点)的边。cross edge
):红色边,它主要是在搜索的时候遇到了一个已经访问过(黑点dfn[u]>dfn[v]
)的结点,但是这个结点 并不是 当前结点的祖先时形成的。forward edge
):蓝色边,它是在搜索的时候遇到子树中的结点(黑点dfn[u]<dfn[v]
)的时候形成的。无向图不存在横叉边和前向边。
Tarjan
算法求强连通分量强连通分量(Strongly Connected Components
),经常简写为:SCC
,有向图中任意两点间可达,实际上形成一个环。
Tarjan
基于对图的深度优先搜索,并对每个节点引入两个值:
dfn[u]
:节点u
的时间戳,记录点u
是DFS
过程中第几个访问的节点。low[u]
:记录节点u
或u
的子树不经过搜索树上的边(树边)能够到达的时间戳最小的节点。dfn[u]==low[u]
。对于每一条与u
相连的边<u,v>
:
v
是u
的子节点,即边<u,v>
是树枝边,则更新low[u]= min(low[u], low[v])
;<u,v>
不是搜索树上的边(反向边),则更新low[u]= min(low[u], dfn[v])
;缩点
SCC
缩成一个点,然后生成一个有向无环图(DAG
),或把一个无向图缩点后变成一棵树,然后可以有很多优秀的性质进行解决。u
开始,对图进行DFS(u)
,点维护dfn[u]
值和low[u]
值。DFS
时先将u
压入栈中,然后遍历邻接边,邻接边定点为v
:
<u,v>
为树边:DFS(v)
,回溯时更新:low[u]=min(low[u],low[v])
。<u,v>
为返祖边:直接更新:low[u]=min(low[u],dfn[v])
。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])
,j
为i
的子节点。
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\n", ans); return 0; }
标签:lan lock ros 搜索 访问 题目 一点 strong 绿色
原文地址:https://www.cnblogs.com/hbhszxyb/p/12801709.html