此题解部分借鉴于九野的博客

题目分析

  • 给定一个 \(n\) 个点 \(m\) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。

  • 允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。

  • 假如没有后面这条限制的话,那图一定是一个无环图。因为有环的话我可以一直在环上跑,所以答案就没有一个上界

  • 没有环的话我萌可以很自然地想到一个 \(O(n)\) 的 拓扑\(dp\) 做法,先做入度为 \(0\) 的点,更新入度不为 \(0\) 的点,把更新后入度为 \(0\) 的点加入队列里,继续做之前的事情

  • 现在考虑有环怎么做

  • 有一个贪心的思路是,到环上就先把环上的点都走完,再从环上任意一点出发

  • 其实这个环可以看做一个大点,也就是我们今天要介绍的主角 \(\to\) 缩点


tarjan缩点

下面这张图是从 九野的博客copy 过来的

tarjan缩点(洛谷P387)-LMLPHP

  • 把可以互相抵达的点集叫做一个连通分量
  • 最大的那个可以互相抵达的点点集即为强连通分量
  • 特别的,单个的点也可以是强连通分量

tarjan的过程就是通过 dfs 找强连通分量(大点)的过程

我们发现 \(7 \to 3\) (红边 / 横叉边)这种边一定不会产生连通分量

而 \(6 \to 4\) (绿边 / 返祖边)这种边一定会产生联通分量

具体来说:具有父子关系的边一定会产生联通分量

我们在dfs的时候需要用一个来保存当前所在路径上的点(栈中所有点一定是有父子关系的)

我们用一些数组来表示dfs的过程

int tim, dfn[MAX], low[MAX]

\(dfn[i]\) 表示遍历到节点 \(i\) 的时间戳(第几次遍历)

\(low[i]\) 表示往上可以到达最早的点

初始化 \(dfn[i] = low[i] = ++tim\)

我们可以根据上面过程的步骤写出以下代码

    for(int i = head[u]; i; i = nex[i]) {
if(dfn[to[i]]) {
if(instack[to[i]]) {
if(low[to[i]] < low[u]) {
low[u] = low[to[i]];
}
}
} else {
dfs(to[i]);
if(low[to[i]] < low[u]) {
low[u] = low[to[i]];
}
}
}

假如当前节点 \(u\), \(dfn[u] == low[u]\) 那就说明栈顶元素一直到节点 \(u\) 都属于一个强联通分量,感性理解

把栈中元素弹出并且把他们都给标记为同一种颜色(同一个强连通分量)

    if(dfn[u] == low[u]) {
++totcol;
do {
int v = stk[top];
col[v] = totcol;
instack[v] = false;
} while(stk[top--] != u);
}

具体实现看代码

\(\color {Deepskyblue} {Code}\)

这里还有一道 tarjan练手好题

05-23 16:51