[P3387] 【模板】缩点 - 强连通分量,dp
Description
给定一个 (n) 个点 (m) 条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大。你只需要求出这个权值和。
允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。
Solution
求强连通分量缩点后按拓扑序 DP
复习一下 Tarjan 求强连通分量
DFS 整棵树,对每个点,维护 (dfn[]) 和 (low[])
(dfn[p]) 表示 DFS 到 (p) 时的时间戳,即 (p) 是第几个被 DFS 的点
(low[p]) 表示从 (p) 点开始,能访问到的点中 (dfn) 最小值
考虑如何求出 (low[p])
我们在 DFS 的过程中,维护一个栈,每碰到一个点就压进栈
如果我们确定了某个点 (q) 属于以 (p) 为 DFS 树根的强连通分量,那么 (q) 会在 (p) 的 DFS 过程结束时被弹栈
枚举 (p) 的所有孩子 (q),有三种情况
-
(q) 是未访问过的点(记 (vis[q]=0)),则 (low[p]=min(low[p],low[q]))
-
(q) 是已经访问过但未出栈的点(记 (vis[q]=1)),则 (low[p]=min(low[p],dfn[q]))
因为此时 (low[q]) 可能未被计算出((p-q) 可能是后向边)
-
(q) 是已经出栈的点,这种情况直接忽略
如何确定一个点 (p) 是 DFS 树根?(dfn[p]=low[p])
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5;
int n, m;
vector<int> g[N], gscc[N];
int a[N], dfn[N], low[N], ind, vis[N], bel[N], val[N], f[N];
stack<int> sta;
vector<vector<int>> scc;
void dfs(int p)
{
dfn[p] = ++ind;
low[p] = dfn[p];
vis[p] = 1;
sta.push(p);
for (int q : g[p])
{
if (vis[q] == 0)
{
dfs(q);
low[p] = min(low[p], low[q]);
}
else if (vis[q] == 1)
{
low[p] = min(low[p], dfn[q]);
}
}
if (dfn[p] == low[p])
{
vector<int> vset;
while (sta.size() && sta.top() != p)
{
vset.push_back(sta.top());
vis[sta.top()] = 2;
sta.pop();
}
vset.push_back(p);
vis[p] = 2;
sta.pop();
scc.push_back(vset);
}
}
void dp(int p)
{
for (int q : gscc[p])
{
if (f[q] == 0)
dp(q);
f[p] = max(f[p], f[q]);
}
f[p] += val[p];
}
signed main()
{
ios::sync_with_stdio(false);
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
vector<pair<int, int>> edges;
for (int i = 1; i <= m; i++)
{
int t1, t2;
cin >> t1 >> t2;
g[t1].push_back(t2);
edges.push_back({t1, t2});
}
for (int i = 1; i <= n; i++)
if (vis[i] == 0)
dfs(i);
for (int comp_id = 0; comp_id < scc.size(); comp_id++)
{
auto vset = scc[comp_id];
for (auto v : vset)
bel[v] = comp_id, val[comp_id] += a[v];
}
// cout << scc.size() << endl;
for (auto edge : edges)
{
auto [p, q] = edge;
p = bel[p];
q = bel[q];
if (p != q)
gscc[q].push_back(p);
}
for (int i = 0; i < scc.size(); i++)
if (f[i] == 0)
dp(i);
int ans = 0;
for (int i = 0; i < scc.size(); i++)
ans = max(ans, f[i]);
cout << ans << endl;
}