缩点[tarjan]

模板题:P3387 【模板】缩点

引语:

缩点是将强连通分量缩为一个点的算法,适用于有环无边权的图。(强连通分量定义:对于一个图的子图,若其中任何点间都存在路径可到达其他任何点,则该子图为一个强连通分量)
重难点数组含义:\(dfn\)\(low\) 两个数组,\(dfn\) 维护 \(dfs\) 序,\(low_{u}\) 维护 \(u\) 或着说是 \(u\) 的子树能够追溯到的最早栈中节点的 \(dfs\) 序,栈维护当前搜索的一条链上的一个个点。


\(tarjan\) 核心 \(dfs\) 代码

void tarjan(int x)  // 缩点(tarjan)
{
    low[x] = dfn[x] = ++timeStamp;	//初始化 low 与 dfn
    stack[++top] = x; instack[x] = 1;
    for (int i = head[x]; i; i = G[i].nxt)
    {
        int v = G[i].v;
        if (!dfn[v])
        {
            tarjan(v);
            low[x] = min(low[x], low[v]);	//维护 low
        }
        else if (instack[v])
            low[x] = min(low[x], low[v]);
    }
    if (dfn[x] == low[x]) // x 为强连通分量 dfs 出发点,开始进行缩点
    {
        int y;
        while (y = stack[top--])
        {
            nodeId[y] = x;
            instack[y] = 0;
            if (x == y) break;
            nodew[x] += nodew[y];
        }
    }
}

\(dfn_x = low_x\),则 \(x\) 为该强连通分量的 \(dfs\) 出发点。此时,栈中 \(x\) 至栈顶,均属于同一强连通分量。


模拟:

  1. 若一个结点 \(x\) 出度为 \(0\),则\(dfn_x = low_x\),自身为一个强连通分量。

  2. 图
    \(dfs\) 过程:1->2->3->4->5->1,这样 \(1\)~\(5\) 的点的 \(low\) 的值均为 \(1\),回溯至 \(1\) 号节点时,\(dfn_1 = low_1\),开始进行缩点。
    也可以查看这两篇博客的模拟过程:\(CSDN_1\)\(CSDN_2\)


代码

#include <iostream>
#include <cstdio>
#include <queue>
#define MAXN 10005
#define MAXM 100005
using namespace std;

int n, m;
struct edge
{
    int u, v, nxt;
}G[MAXM], G_[MAXM];
int head[MAXN], cntEdge, head_[MAXN], cntEdge_;
int dfn[MAXN], low[MAXN], timeStamp; // 记录时间戳
int stack[MAXN], top, instack[MAXN]; // 记录搜索链
int nodew[MAXN], nodeId[MAXN];  //点权,及点对应的强连通分量 dfs 出发点(有点并查集的感觉)
int ind[MAXN], dis[MAXN];   // 缩点后点的入度

int read()
{
    int x = 0; char ch = getchar();
    while (ch < '0' || ch > '9')
        ch = getchar();
    while (ch >= '0' && ch <= '9')
    {
        x = (x << 3) + (x << 1) + (ch ^ 48);
        ch = getchar();
    }
    return x;
}

inline void addEdge(edge *G, int *head, int &cntEdge, int u, int v) 
{ 
    ++cntEdge;
    G[cntEdge].u = u;
    G[cntEdge].v = v;
    G[cntEdge].nxt = head[u];
    head[u] = cntEdge;
}

void init()
{
    n = read(), m = read();
    for (int i = 1; i <= n; ++i)
        nodew[i] = read();
    for (int i = 1; i <= m; ++i)
    {
        int u = read(), v = read();
        addEdge(G, head, cntEdge, u, v);
    }
}

void tarjan(int x)  // 缩点(tarjan)
{
    low[x] = dfn[x] = ++timeStamp;
    stack[++top] = x; instack[x] = 1;
    for (int i = head[x]; i; i = G[i].nxt)
    {
        int v = G[i].v;
        if (!dfn[v])
        {
            tarjan(v);
            low[x] = min(low[x], low[v]);
        }
        else if (instack[v])
            low[x] = min(low[x], low[v]);
    }
    if (dfn[x] == low[x]) // x 为强连通分量 dfs 出发点
    {
        int y;
        while (y = stack[top--])
        {
            nodeId[y] = x;
            instack[y] = 0;
            if (x == y) break;
            nodew[x] += nodew[y];
        }
    }
}

int topo()
{
    queue <int> q;
    for (int i = 1; i <= n; ++i)
        if (nodeId[i] == i && !ind[i])
        {
            q.push(i);
            dis[i] = nodew[i];
        }
    while (!q.empty())
    {
        int u = q.front(); q.pop();
        for (int i = head_[u]; i; i = G_[i].nxt)
        {
            int v = G_[i].v;
            dis[v] = max(dis[v], dis[u] + nodew[v]);
            --ind[v];
            if (!ind[v])
                q.push(v);
        }
    }
    int ret = 0;
    for (int i = 1; i <= n; ++i)
        ret = max(ret, dis[i]);
    return ret;
}

void solve()
{
    for (int i = 1; i <= n; ++i)
        if (!dfn[i])
            tarjan(i);
    for (int i = 1; i <= m; ++i)
    {
        int u = nodeId[G[i].u], v = nodeId[G[i].v];
        if (u != v)
        {
            addEdge(G_, head_, cntEdge_, u, v);
            ++ind[v];
        }
    }
    printf("%d\n", topo());
}

int main()
{
    init();
    solve();
    return 0;
}
posted @ 2022-10-18 17:07  晨曦墨凝  阅读(39)  评论(2)    收藏  举报