tarjan算法求缩点

【模板】缩点

题目描述

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

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

输入格式

第一行两个正整数 n , m n,m n,m

第二行 n n n 个整数,其中第 i i i 个数 a i a_i ai 表示点 i i i 的点权。

第三至 m + 2 m+2 m+2 行,每行两个整数 u , v u,v u,v,表示一条 u → v u\rightarrow v uv 的有向边。

输出格式

共一行,最大的点权之和。

样例 #1

样例输入 #1

2 2
1 1
1 2
2 1

样例输出 #1

2

提示

对于 100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 4 1\le n \le 10^4 1n104 1 ≤ m ≤ 1 0 5 1\le m \le 10^5 1m105 0 ≤ a i ≤ 1 0 3 0\le a_i\le 10^3 0ai103

题解:

#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 1e5 + 1, N = 1e4 + 1;
int n, m, val[N], cnt = 0, head[N] = { 0 }, vis[N] = { 0 }, low[N], dfn[N], times = 0, st[N], top = -1;
int belong[N], rep[N] = { 0 }, num = 0; long long tans = 0, ans = 0;

struct Edge {
    int from, to, next;
}e[maxn];

void add(int u, int v) {
    cnt++;
    e[cnt].from = u;
    e[cnt].to = v;
    e[cnt].next = head[u];
    head[u] = cnt;
}

int read() {
    int x = 0, f = 1; char ch = getchar();
    while (ch < '0' || ch>'9') { if (ch == '-')f = -1; ch = getchar(); }
    while (ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
    return f * x;
}

void tarjan(int k) {
    vis[k] = 1;
    low[k] = dfn[k] = ++times;
    st[++top] = k;
    int r = top;
    for (int i = head[k]; i; i = e[i].next) {
        int v = e[i].to;
        if (!dfn[v]) {
            tarjan(v);
            low[k] = min(low[k], low[v]);
        }
        //只有v被访问过且v还不属于任何强连通分量的情况下才更新low[u]
        else if (vis[v]) {
            low[k] = min(low[k], dfn[v]);
        }
    }
    //low[k]==dfn[k]表明找到了一个强连通分量
    //单个点也算一个强连通分量
    if (low[k] == dfn[k]) {
    	//num表示强连通分量的个数
        num++;
        //将同属于一个强连通分量的一坨点出栈
        for (int i = r; i <= top; i++) {
        	//vis标记为0表明这个点已经找到了属于它的强连通分量
            vis[st[i]] = 0;
            //belong[y]=x;表示y这个点属于编号为x的强连通分量
            belong[st[i]] = num;
            //rep数组存放单个强连通分量的点权之和
            rep[num] += val[st[i]];
        }
        top = r - 1;
    }
}

//重新建图
void rebuild() {
    cnt = 0;
    memset(head, 0, sizeof(head));
    for (int i = 1; i <= m; i++) {
        int u = e[i].from, v = e[i].to;
        if (belong[u] != belong[v]) {
            add(belong[u], belong[v]);
        }
    }
}

//进行一遍完整的dfs就可找到答案
void dfs(int k,int sum) {
    sum += rep[k];
    tans = sum > tans ? sum : tans;
    for (int i = head[k]; i; i = e[i].next) {
        int t = e[i].to;
        dfs(t, sum);
    }
}

int main() {
    n = read(); m = read();
    for (int i = 1; i <= n; i++)//读入各点权
        val[i] = read();
    for (int i = 1; i <= m; i++) {
        int u, v; u = read(), v = read();
        add(u, v);
    }
    for (int i = 1; i <= n; i++) {
        if (dfn[i] == 0)
            tarjan(i);
    }
    rebuild();
    memset(vis, 0, sizeof(vis));
    //图不一定连通
    for (int i = 1; i <= num; i++) {
        tans = 0;
        dfs(i, 0);
        ans = tans > ans ? tans : ans;
    }
    cout << ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值