题解:P3119 [USACO15JAN] Grass Cownoisseur G

题意

给定一个含 \(n\) 个节点 \(m\) 条边的有向图,求从节点 \(1\) 出发若最多可以逆向走一条边,最多可以经过多少个节点?

分析

注意到一个强连通分量中的所有点都可以在不走反边的情况下到达,因此先对原图求强连通分量并缩点,记录每个强连通分量的大小 \(sz_u\)。建新图,插入边 \((u, v)\) 时认为它的边权是 \(sz_v\),则原问题等价于求新图在至多走一条反边的情况下,从 \(1\) 开始的最长路。

这是一个典型的 \(k\) - 限制最值路问题,考虑分层图,复制一层原图,并在插入边 \((u, v)\) 时再插入一条边 \((v, u + cnt)\)(\(cnt\) 表示强连通分量个数),表示一条反边。因为只有从第一层到第二层的边,这正好对应了“只走至多一条反边”的限制。用 spfa 跑一边最长路即可,最后答案即为 \(\max(dis_n, dis_{n + cnt})\)。

注意,若原图即为强连通图,这时应直接特判输出 \(sz_1\)。因为这时候没有边,最长路并没有运行。

代码

#include <bits/stdc++.h>
using namespace std;
using pii = pair<int, int>;
const int N = 1e5 + 5;
int n, m;
int idx, top, cnt, dfn[N], low[N], stk[N], id[N], sz[N];
int deg[N], dis[N << 1];
bool in[N << 1];
vector<int> g[N];
vector<pii> dag[N << 1];
void scc(int u) {
    dfn[u] = low[u] = ++idx;
    stk[++top] = u, in[u] = true;
    for (int v : g[u]) {
        if (!dfn[v]) {
            scc(v);
            low[u] = min(low[u], low[v]);
        } else if (in[v])
            low[u] = min(low[u], dfn[v]);
    }
    if (low[u] == dfn[u]) {
        cnt++;
        int t;
        do {
            t = stk[top--];
            in[t] = false, id[t] = cnt, sz[cnt]++;
        } while (t != u);
    }
    return;
}
void init() {
    memset(in, false, sizeof in);
    memset(dis, -1, sizeof dis);
    return;
}
void spfa(int s) {
    queue<int> q;
    in[s] = true, dis[s] = 0;
    q.push(s);
    while (q.size()) {
        int u = q.front();
        q.pop();
        in[u] = false;
        for (auto [v, w] : dag[u]) {
            int nd = dis[u] + w;
            if (nd > dis[v]) {
                dis[v] = nd;
                if (in[v])
                    continue;
                q.push(v);
                in[v] = true;
            }
        }
    }
    return;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
    }
    for (int u = 1; u <= n; u++)
        if (!dfn[u])
            scc(u);
    if (cnt == 1) {
        printf("%d\n", sz[1]);
        return 0;
    }
    for (int u = 1; u <= n; u++) {
        for (int v : g[u]) {
            int x = id[u], y = id[v];
            if (x == y)
                continue;
            dag[x].push_back({y, sz[y]}), dag[x + cnt].push_back({y + cnt, sz[y]}); 
            // 插入原边并复制到第二层
            dag[y].push_back({x + cnt, sz[x]});
            // 插入跨层边(反边)
        }
    }
    init(); // spfa 和 tarjan 共用了一个 in 数组,应该清空
    int s = id[1]; // 注意是从 1 所在 SCC 跑 spfa 而不是 1 这个节点
    spfa(s);
    printf("%d\n", max(dis[s], dis[s + cnt]));
    return 0;
}
posted @ 2026-10-06 13:38  Hyazinth  阅读(8)  评论(0)    收藏  举报