UVa 1609 Foul Play

题目描述

一场欧洲足球锦标赛有 nnn 支参赛队伍,且 nnn222 的幂。比赛采用淘汰赛制:

  • 第一轮进行 n/2n/2n/2 场比赛,每队与另一队比赛;
  • 每轮结束后,只有获胜的队伍晋级下一轮;
  • 第二轮进行 n/4n/4n/4 场比赛,依此类推,直到决出冠军。

你最喜欢的队伍是 111 号队(荷兰队)。已知:

  1. 队伍 111 至少能打败其他队伍中的一半;
  2. 对于每一支队伍 111 无法直接打败的队伍 ttt,都存在另一支队伍 t′t'tt′t't 能打败 ttt,且 t′t't 被队伍 111 打败。

你已知每两支队伍之间的胜负关系(用一个 n×nn \times nn×n 的二进制矩阵表示)。请构造一个赛程安排,使得队伍 111 能够成为冠军。

输入格式

  • 第一行:队伍数 nnn2≤n≤10242 \le n \le 10242n1024,且 nnn222 的幂)
  • 接下来 nnn 行:每行一个长度为 nnn 的字符串,表示该队伍对其他队伍的胜负关系(1 表示胜,0 表示负,对角线为 0

输出格式

输出 n−1n - 1n1 行,每行两个整数 x yx\ yx y,表示 xxxyyy 进行比赛。赛程必须保证队伍 111 获胜。


题目分析

关键性质

设:

  • 黑色队伍:队伍 111 无法打败的队伍;
  • 灰色队伍:队伍 111 能打败,且能打败至少一个黑色队伍的队伍;
  • 白色队伍:队伍 111 能打败,但不能打败任何黑色队伍的队伍。

根据题目条件:

  1. 队伍 111 至少能打败一半的队伍,因此黑色队伍不超过 n/2−1n/2 - 1n/21(不包括自己);
  2. 对每个黑色队伍 ttt,存在一个灰色队伍 t′t't 能打败 ttt,且 t′t't 被队伍 111 打败。

构造思路

我们可以将每一轮的比赛安排分为四个阶段:

  1. 消灭黑色:用灰色或白色队伍去淘汰黑色队伍;
  2. 安排队伍 111:让队伍 111 与一个它能打败的队伍比赛;
  3. 黑色自相残杀:若还有未被淘汰的黑色队伍,让它们互相淘汰;
  4. 自由配对:剩下的队伍任意配对。

这样能确保:

  • 所有黑色队伍在遇到队伍 111 之前就被淘汰;
  • 队伍 111 在整个比赛中只遇到它能打败的对手;
  • 最终队伍 111 夺冠。

解题步骤

  1. 读入 nnn 和胜负矩阵;
  2. 对当前轮次的队伍进行分类:黑色、灰色、白色;
  3. 按四阶段策略安排比赛:
    • 阶段一:用灰色/白色淘汰黑色;
    • 阶段二:安排队伍 111 的比赛;
    • 阶段三:黑色队伍互相淘汰;
    • 阶段四:剩余队伍自由配对;
  4. 递归处理下一轮晋级队伍,直到只剩一支队伍。

代码实现

// Foul Play
// UVa ID: 1609
// Verdict: Accepted
// Submission Date: 2025-11-23
// UVa Run Time: 0.140s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net

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

vector<string> win;
int n;
vector<bool> used;

void scheduleRound(vector<int> teams) {
    if (teams.size() == 1) return;
    
    // 分类队伍
    vector<int> black, gray, white;
    for (int team : teams) {
        if (team == 1) continue;
        if (win[1][team] == '0') black.push_back(team);
        else {
            // 检查是否是灰色队伍(能打败某个黑色队伍)
            bool isGray = false;
            for (int b : black) {
                if (win[team][b] == '1') {
                    isGray = true;
                    break;
                }
            }
            if (isGray) gray.push_back(team);
            else white.push_back(team);
        }
    }
    
    vector<int> nextRound;
    used.assign(n + 1, false);
    
    // 阶段1:用灰色队伍消灭黑色队伍
    for (int b : black) {
        if (used[b]) continue;
        bool eliminated = false;
        for (int& g : gray) {
            if (!used[g] && win[g][b] == '1') {
                cout << g << " " << b << endl;
                used[g] = used[b] = true;
                nextRound.push_back(g);
                eliminated = true;
                break;
            }
        }
        // 如果找不到灰色队伍,用白色队伍
        if (!eliminated) {
            for (int& w : white) {
                if (!used[w] && win[w][b] == '1') {
                    cout << w << " " << b << endl;
                    used[w] = used[b] = true;
                    nextRound.push_back(w);
                    eliminated = true;
                    break;
                }
            }
        }
    }
    
    // 阶段2:安排队伍1的比赛
    if (!used[1]) {
        for (int team : teams) {
            if (team != 1 && !used[team] && win[1][team] == '1') {
                cout << "1 " << team << endl;
                used[1] = used[team] = true;
                nextRound.push_back(1);
                break;
            }
        }
    }
    
    // 阶段3:剩下的黑色队伍自相残杀
    vector<int> remainingBlack;
    for (int b : black) if (!used[b]) remainingBlack.push_back(b);
    for (int i = 0; i < remainingBlack.size(); i += 2) {
        if (i + 1 < remainingBlack.size()) {
            int a = remainingBlack[i], b = remainingBlack[i + 1];
            cout << a << " " << b << endl;
            used[a] = used[b] = true;
            if (win[a][b] == '1') nextRound.push_back(a);
            else nextRound.push_back(b);
        }
    }
    
    // 阶段4:剩下的队伍任意配对
    vector<int> remaining;
    for (int team : teams) if (!used[team]) remaining.push_back(team);
    for (int i = 0; i < remaining.size(); i += 2) {
        if (i + 1 < remaining.size()) {
            int a = remaining[i], b = remaining[i + 1];
            cout << a << " " << b << endl;
            if (win[a][b] == '1') nextRound.push_back(a);
            else nextRound.push_back(b);
        }
    }
    
    scheduleRound(nextRound);
}

int main() {
    while (cin >> n) {
        win.resize(n + 1);
        used.resize(n + 1);
        for (int i = 1; i <= n; i++) {
            cin >> win[i];
            win[i] = " " + win[i];
        }
        
        vector<int> teams(n);
        for (int i = 0; i < n; i++) teams[i] = i + 1;
        
        scheduleRound(teams);
    }
    return 0;
}

复杂度分析

  • 时间复杂度:每轮处理 O(n2)O(n^2)O(n2),总轮数为 O(log⁡n)O(\log n)O(logn),因此总复杂度为 O(n2log⁡n)O(n^2 \log n)O(n2logn),在 n≤1024n \le 1024n1024 时完全可行。
  • 空间复杂度O(n2)O(n^2)O(n2) 用于存储胜负矩阵。

总结

本题是一道构造型题目,关键在于利用题目给出的两个性质,将队伍合理分类,并设计一个四阶段比赛安排策略,确保队伍 111 不会遇到它无法打败的对手。这种分类+阶段化处理的方法在构造题中非常实用,值得掌握。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值