缩点Tarjan

解释

缩点,就是把一张有向有环图中的环缩成一个个点,形成一个有向无环图
1.dfn和low的含义
tarjan算法有两个核心数组:dfn和low,同样也需要一个栈,辅助存储强连通分量。dfn是“时间戳”,dfn[i]说明i点在什么时间被遍历的。low[i]是i可回溯到的最早时间戳的点的时间戳(见下面对low值的计算),默认值为dfn[i],举个例子:如果i到j有一条路径,j到i也有一条路径,那么就使 i -> j -> i 的环中所以点中 low 变为最早时间戳 并入栈,打上在栈内的标记in。

对于一个有向图的DFS的搜索树(i 可以到 j,j 不一定能到 i),如下

在这里插入图片描述
里面的强连通分量有 { 6 , 7 , 8 } ,{ 4 } , { 3 } , { 2 } , { 1 } , { 9 }

而强连通分量产生的环 { 6 , 7 , 8 } 是有父子关系的,所以在 { 2 , 3 , 4 , 9 } 这个环不是强连通分量,因为搜索树是有向的(3 , 9 可以到 4 , 而 4 不能访问回去)

在用Tarjan算法时,栈中的点一定是有父子关系的

  1. dfn[ i ] 数组表示遍历到点 i 时DFS的次数
  2. low[ i ] 数组表示点 i 到栈中
    在搜索的过程中会先搜索 1——2——3——4,然后在到 9 的时候就会有这种情况
    在这里插入图片描述
    在用Tarjan算法时,用栈存储的点有 { 1 , 2 , 3 , 9 } ,这时还未遍历点 4,点 4 不在栈中,与点 9 没有父子关系

遍历到点 4 ,此时点 4 无法往下遍历,且与栈中点 9 无父子关系,只能退出栈,有强连通分量 { 4 },然后回溯

依次退栈,有强连通分量 { 3 },{ 9 },{ 2 },{ 1 }

在这里插入图片描述
遍历到点 7,栈中的点有 { 5, 6 , 7 }

往下遍历到点 8,栈中点 { 5, 6,7,8 },到点 8 后往下遍历到点 6,点 6 为栈中点,则点 8 到栈中深度最小的点为点 6,low[ 8 ] = 6

low[8] < low[7],说明点 6 可以到点 8,点 8 也可以到点 6 ,这其中的点都可以相互到达

然后就将放入栈中的点 8,7 取出(按入栈顺序),最后取出点 6 自己时停止

则 { 6,7 , 8 } 为强连通分量

代码

题目luoguP3387 【模板】缩点

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N = 1e4 + 5;
const int M = 1e5 + 5;
struct node{
    int v, u;
    int next;
}e[M], ed[M];
int p[N], head[N], in[N];
int dfn[N], low[N], sd[N], tim;
int stac[N], vis[N], top;
int n, m, cnt, ans[N];
void add1(int u, int v) {
    e[++cnt].v = v;
    e[cnt].u = u;
    e[cnt].next = head[u];
    head[u] = cnt;
}
void add2(int u, int v) {
    ed[++cnt].v = v;
    ed[cnt].u = u;
    ed[cnt].next = head[u];
    head[u] = cnt;
    in[v]++;
}
void tarjan(int u) {
    low[u] = dfn[u] = ++tim; //tim表示在栈中的编号
    stac[++top] = u, vis[u] = 1;
    for(int i = head[u]; i; i = e[i].next) { //搜索相连节点
        int v = e[i].v;
        if(!dfn[v]) { //没搜索过
            tarjan(v);
            low[u] = min(low[u], low[v]); //更新所能到的上层节点
        } else if(vis[v]) low[u] = min(dfn[v], low[u]);//在队中 找到栈中最上端的节点
        //dfn是栈中编号或时间戳,如果s在栈中,则x到栈中最上端节点为dfn[s]
    }
    if(dfn[u] == low[u]) { //找到了一个强联通分量,开始弹栈,直到弹到当前这一个点为止
        int v;
        while(v = stac[top--]) {
            sd[v] = u; // 染色
            vis[v] = 0;
            if(u == v) break;
            p[u] += p[v]; // 统计每一个颜色的点的总权值
        }
    }
}
int tuopu() { // 拓扑排序
    queue<int> q;
    for(int i = 1; i <= n; ++i) {
        if(!in[i]) {
            q.push(i);
            ans[i] = p[i];
        }
    }
    while(!q.empty()) {
        int u = q.front();
        q.pop();
        for(int i = head[u]; i; i=ed[i].next) {
            int v = ed[i].v;
            ans[v] = max(ans[v], ans[u]+p[v]); // dp求最大路
            if(--in[v] == 0) q.push(v);
        }
    }
    int sum = 0;
    for(int i = 1; i <= n; ++i) // 在每个点的最大路中找到最大的
        sum = max(sum, ans[i]);
    return sum;
}
int main() {
    cin >> n >> m;
    for(int i = 1; i <= n; ++i)
        cin >> p[i];
    for(int i = 1; i <= m; ++i) {
        int u, v;
        cin >> u >> v;
        add1(u, v);
    }
    for(int i = 1; i <= n; ++i) //找强连通分量
        if(!dfn[i]) tarjan(i);

    cnt = 0;
    memset(head, 0, sizeof(head)); //初始化
    for(int i = 1; i <= m; ++i) {
        int u = sd[e[i].u], v = sd[e[i].v];// 建新图
        if(u != v) add2(u, v);
    }
    int ans = tuopu();
    printf("%d\n", ans);
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光—暗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值