[P3387] 【模板】缩点

[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;
}
原文地址:https://www.cnblogs.com/mollnn/p/14560452.html