[Codeforeces 894E. Ralph and Mushrooms] 缩点+拓扑

[Codeforeces 894E. Ralph and Mushrooms] 缩点+拓扑

分类:Data Structure scc

1. 题目链接

[Codeforeces 894E. Ralph and Mushrooms]

2. 题意描述

n 个顶点m条边的有向图。第 i 条边的初始边权为wi,第 k 次经过第i条边的权值为 wi01(k1) 。当 wi01(k1)<0 时,边权为 0
然后,你从点s出发,问走过的所有边权和。
数据范围:
1n106,0m106

3. 解题思路

虽然这题思路很清晰,但是总是手残,写出无限多的Bug。比如,DAG当做树来搞,各种写错,而且代码写的不优美,将就着看吧。
首先,对有向图进行缩点。那么同一个强连通分量里面的所有边都可以被完全利用,各个强连通分量之间的边,最多只能经过一次。
然后,对于缩点之后的DAG,拓扑排序可以求出到每个连通分量的最大边权和,就可以统计出答案了。

4. 实现代码

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef unsigned long long ull;

const int inf = 0x3f3f3f3f;
const ull infl = 0x3f3f3f3f3f3f3f3f;

#ifdef ___LOCAL_WONZY___
void debug() { cout << endl; }
template<typename T, typename ...R> void debug(T f, R ...r) {
    cout << "[" << f << "]";
    debug(r...);
}
#endif // ___LOCAL_WONZY___

const int MAXN = 1000006;
int n, m, s;
struct Graph {
    int head[MAXN], etot;
    struct Edge {
        int v, next;
        ull w;
    } edge[MAXN];
    void ini(int n) {
        etot = 0;
        for (int i = 0; i <= n; ++i) head[i] = -1;
    }
    void add(int u, int v, ull w) {
        edge[etot].v = v; edge[etot].w = w;
        edge[etot].next = head[u]; head[u] = etot ++;
    }
} G1, G2;
int du[MAXN];
int Low[MAXN], DFN[MAXN], Stack[MAXN], Belong[MAXN];
ull wsum[MAXN];
int Index, top;
int scc;//强连通分量的个数
bool Instack[MAXN];
ull pre[MAXN], ppre[MAXN], psz, ans;

void Tarjan(int u) {
    int v;
    Low[u] = DFN[u] = ++Index;
    Stack[top++] = u;
    Instack[u] = true;
    for (int i = G1.head[u]; ~i; i = G1.edge[i].next) {
        v = G1.edge[i].v;
        if (!DFN[v]) {
            Tarjan(v);
            if (Low[u] > Low[v]) Low[u] = Low[v];
        }
        else if (Instack[v] && Low[u] > DFN[v]) Low[u] = DFN[v];
    }
    if (Low[u] == DFN[u]) {
        ++ scc;
        do {
            v = Stack[--top];
            Instack[v] = false;
            Belong[v] = scc;
        }
        while ( v != u);
    }
}

inline ull get_contri(const ull& w) {
    int pos = upper_bound(pre, pre + psz, w) - pre;
    return w * pos - ppre[pos - 1];
}

ull dp[MAXN];
bool flag[MAXN];
void dfs(int u) {
    flag[u] = 1;
    for (int i = G2.head[u]; ~i; i = G2.edge[i].next) {
        int v = G2.edge[i].v;
        if (!flag[v]) dfs(v);
    }
}

void bfs() {
    dfs(Belong[s]);
    for (int u = 1; u <= scc; ++u) {
        if (!flag[u]) continue;
        for (int i = G2.head[u]; ~i; i = G2.edge[i].next) {
            int v = G2.edge[i].v;
            ++ du[v];
        }
    }
    int u, v; ull w;
    queue<int> q;
    q.push(Belong[s]);
    dp[Belong[s]] = wsum[Belong[s]];
    while (!q.empty()) {
        u = q.front(); q.pop();
        for (int i = G2.head[u]; ~i; i = G2.edge[i].next) {
            v = G2.edge[i].v; w = G2.edge[i].w;
            -- du[v];
            if (du[v] == 0) q.push(v);
            dp[v] = max(dp[v], dp[u] + wsum[v] + w);
        }
    }
    for (int i = 1; i <= scc; ++i) ans = max(ans, dp[i]);
}

int main() {
#ifdef ___LOCAL_WONZY___
    freopen("input.txt", "r", stdin);
#endif // ___LOCAL_WONZY___
    pre[0] = ppre[0] = 0;
    for (int i = 1; i < MAXN; ++i) {
        pre[i] = pre[i - 1] + i;
        ppre[i] = ppre[i - 1] + pre[i];
        ++ psz;
        //手残把pre写成了ppre,无限WA
        if(pre[i] > 120000000) break;
    }
    int u, v; ull w; ans = 0;
    scanf("%d %d", &n, &m);
    G1.ini(n);
    for (int i = 1; i <= m; ++i) {
        scanf("%d %d %llu", &u, &v, &w);
        G1.add(u, v, w);
    }
    scanf("%d", &s);
    Index = scc = top = 0;
    for (int i = 1; i <= n; i++) if (!DFN[i]) Tarjan(i);
    G2.ini(scc);
    for (u = 1; u <= n; ++u) {
        for (int i = G1.head[u]; ~i; i = G1.edge[i].next) {
            v = G1.edge[i].v; w = G1.edge[i].w;
            if (Belong[u] != Belong[v]) G2.add(Belong[u], Belong[v], w);
            else wsum[Belong[v]] += get_contri(w);
        }
    }
    bfs();
    printf("%llu\n", ans);
#ifdef ___LOCAL_WONZY___
    cout << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC * 1000 << "ms." << endl;
#endif // ___LOCAL_WONZY___
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值