P2419 [USACO08JAN] Cow Contest S

GESP8 冲刺课堂页:把“谁一定打得过谁”做成一张表 —— 传递闭包(Floyd 框架)。

图论传递闭包Floyd 框架N≤100

一、先回答问题

结论

一头牛的名次能确定,当且仅当它和其余每一头牛的强弱关系都已知(直接或间接)。

样例答案:牛 2、牛 5 共 2 头。

教学安排

① 把胜负关系填进 win[i][j] 表 → ② 用 Floyd 框架补全间接关系 → ③ 数每头牛已知几对 → ④ 满 4 对的就确定名次。

二、核心观察

建模

win[i][j]=1 表示“i 一定比 j 强”。胜负有传递性:A 胜 B、B 胜 C ⟹ A 胜 C,所以可以用传递闭包把所有能推出的关系补齐。

补全:win[i][j] |= win[i][k] && win[k][j]

和 Floyd 最短路同一个三层循环框架,这里求的是“可达 / 强弱”,不是距离。

判定条件

牛 i 名次确定 ⟺ 对所有 j≠iwin[i][j]win[j][i] 至少一个为 1。

也就是:它和其余 n-1每一对都分出了胜负——赢了多少、输了多少都清楚,名次自然唯一。

赢过的输给的两者相加 = n-1 → 确定

三、互动演示 · win 关系表

第 0 步

行 i 打得过 列 j ?(样例 5 头牛)
直接比赛 传递推出 未知

四、自己选答案

问题 1

一头牛“名次确定”靠什么判断?

问题 2

win[i][j] |= win[i][k] && win[k][j] 在算什么?

问题 3

样例里牛 5 为什么名次能确定?

五、手推结果(样例 5 头牛)

赢过(含间接)输给(含间接)已知对数名次确定?
12, 52 / 4
251, 3, 44 / 4是 ✓(第 4 名)
32, 543 / 4
42, 3, 53 / 4
51, 2, 3, 44 / 4是 ✓(第 5 名)

只有“赢过 + 输给”加起来正好 4 头的牛,名次才唯一 → 答案 = 2。

六、C++14 参考代码

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n, m;
    cin >> n >> m;

    vector<vector<int>> win(n + 1, vector<int>(n + 1, 0));
    for (int e = 0; e < m; e++) {
        int a, b;
        cin >> a >> b;
        win[a][b] = 1;            // a yiding sheng b
    }

    // Floyd kuangjia: chuandi bibao
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                if (win[i][k] && win[k][j]) win[i][j] = 1;

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        bool ok = true;
        for (int j = 1; j <= n; j++) {
            if (i == j) continue;
            if (!win[i][j] && !win[j][i]) { ok = false; break; }
        }
        if (ok) ans++;
    }

    cout << ans << '\n';
    return 0;
}

七、进阶版 · bitset 提速

迁移

N 更大时,可以用 bitset 把每一行压成位向量,传递闭包的内层循环交给 CPU 一次处理 64 位:

bitset<105> win[105];
if (win[i][k]) win[i] |= win[k]; // 速度约 ÷64

本题 N≤100,普通三层循环已足够;bitset 是“同一个传递闭包,换更快的实现”,留作进阶练习。

同类 / 迁移训练

巩固

B3647 Floyd 模板 —— 对照“最短路”与“传递闭包”是同一框架。