[USACO03FALL / HAOI2006] 受欢迎的牛 G

OJ: luogu

题目 ID: P2341

标签: sccdag

日期: 2025-12-29 11:04

题目解析

这道题是 Tarjan 缩点 + DAG 出度分析 的经典应用。

题目解析

  1. 喜欢关系的传递性: 题目指出“喜欢”具有传递性。这意味着如果点 和点 处于同一个强连通分量(SCC)中,那么该分量内的所有奶牛都互相喜欢。
  2. 缩点的必要性: 我们可以将所有的 SCC 缩成一个点。缩点后,整个图变成了一个 DAG(有向无环图)
  3. 谁能成为明星?
  • 明星奶牛必须被“所有”奶牛喜欢。在缩点后的 DAG 中,这等价于:该点必须能被 DAG 中所有其他节点到达
  • 在 DAG 中,如果一个点的 出度为 0,说明它无法到达别人。
  • 关键结论:如果图中只有一个出度为 0 的 SCC,那么这个 SCC 里的所有奶牛都是明星。
  • 为什么? 如果有多个出度为 0 的 SCC(假设为 和 ),那么 里的奶牛无法到达 ,反之亦然。此时没有任何一头牛能被所有人喜欢。
  1. 算法流程
  • 运行 Tarjan 算法求出所有 SCC。
  • 统计每个 SCC 的出度。
  • 统计出度为 0 的 SCC 个数。
  • 如果个数为 1,答案就是该 SCC 包含的奶牛数量;否则答案为 0。

代码实现

根据你的框架完成如下:

cpp

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github : https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * date: 2025-12-29 10:52:38
 */
#include <bits/stdc++.h>
using namespace std;
typedef  long long ll;
typedef  unsigned long long ull;

const int maxn = 2e6+5;
const int maxe = 4e6+5;
const int mod = 1e9+7;

int n, m;

struct linkList {
    typedef struct {int u,v,w,next;} edge;
    edge e[maxe];
    int h[maxn], edge_cnt = 0;
    linkList(){
        reset();
    }

    void reset() {
        edge_cnt = 0;
        memset(h, -1, sizeof(h));
    }

    void add(int u,int v,int w=0){
        e[edge_cnt] = {u,v,w,h[u]};
        h[u] = edge_cnt++;
    }

    int operator()(int u){ return h[u]; }
    edge& operator[](int i){ return e[i]; }
} e;

struct TarjanScc {
    int n, timer;
    std::stack<int> st;
    bool in_stack[maxn];
    int dfn[maxn], low[maxn], scc_id[maxn];
    int scc_cnt; 
    int sz[maxn]; // 新增:记录每个 SCC 包含的节点数量

    void set(int _n) {
        n = _n;
        timer = scc_cnt = 0;
        memset(dfn, 0, sizeof(dfn));
        memset(in_stack, 0, sizeof(in_stack));
        memset(sz, 0, sizeof(sz));
    }

    void dfs(int u) {
        dfn[u] = low[u] = ++timer;
        st.push(u);
        in_stack[u] = true;

        for (int i = e(u); ~i ; i = e[i].next) {
            int v = e[i].v;
            if (!dfn[v]) {
                dfs(v);
                low[u] = std::min(low[u], low[v]);
            } else if (in_stack[v]) {
                low[u] = std::min(low[u], dfn[v]);
            }
        }

        if (low[u] == dfn[u]) {
            scc_cnt++;
            while (1) {
                int v = st.top(); st.pop();
                in_stack[v] = 0;
                scc_id[v] = scc_cnt;
                sz[scc_cnt]++; // 累加该 SCC 的大小
                if (v == u) break;
            }
        }
    }

    void solve() {
        for (int i = 1; i <= n; i++) {
            if (!dfn[i]) dfs(i);
        }
    }
} tscc;

int out_degree[maxn]; // 记录每个 SCC 的出度

signed main () {
    ios::sync_with_stdio(false); cin.tie(0);
    
    if(!(cin >> n >> m)) return 0;
    
    for(int i = 0; i < m; i++) {
        int u, v;
        cin >> u >> v;
        e.add(u, v);
    }

    tscc.set(n);
    tscc.solve();

    // 遍历所有原图的边,统计 SCC 的出度
    for(int i = 0; i < e.edge_cnt; i++) {
        int u = e.e[i].u;
        int v = e.e[i].v;
        // 如果边连接了两个不同的 SCC
        if(tscc.scc_id[u] != tscc.scc_id[v]) {
            out_degree[tscc.scc_id[u]]++;
        }
    }

    int zero_out_cnt = 0; // 出度为 0 的 SCC 数量
    int star_scc_idx = 0; // 记录出度为 0 的 SCC 编号

    for(int i = 1; i <= tscc.scc_cnt; i++) {
        if(out_degree[i] == 0) {
            zero_out_cnt++;
            star_scc_idx = i;
        }
    }

    // 判定:如果有且仅有一个出度为 0 的 SCC
    if(zero_out_cnt == 1) {
        cout << tscc.sz[star_scc_idx] << endl;
    } else {
        // 如果有多个出度为 0 的 SCC,或者没有点(n=0),则没有明星
        cout << 0 << endl;
    }

    return 0;
}

关键细节说明

  1. tscc.sz 数组:我在 TarjanScc 结构体中添加了一个 sz 数组,用来在弹出栈时直接统计该强连通分量里有多少头奶牛。
  2. 出度判定:明星奶牛在 DAG 的“末端”,所以它们所在分量的出度必须为 0。如果有多于一个分量出度为 0,它们之间无法互通,就不存在能被“所有人”喜欢的奶牛。