P3243 [HNOI2015] 菜肴制作(图论)

P3243 [HNOI2015] 菜肴制作

题目描述

知名美食家小 A 被邀请至 ATM 大酒店,为其品评菜肴。ATM 酒店为小 A 准备了 \(n\) 道菜肴,酒店按照为菜肴预估的质量从高到低给予 \(1\)\(n\) 的顺序编号,预估质量最高的菜肴编号为 \(1\)

由于菜肴之间口味搭配的问题,某些菜肴必须在另一些菜肴之前制作,具体的,一共有 \(m\) 条形如 \(i\) 号菜肴必须先于 \(j\) 号菜肴制作的限制,我们将这样的限制简写为 \((i,j)\)

现在,酒店希望能求出一个最优的菜肴的制作顺序,使得小 A 能尽量先吃到质量高的菜肴:

也就是说,

  1. 在满足所有限制的前提下,\(1\) 号菜肴尽量优先制作。

  2. 在满足所有限制,\(1\) 号菜肴尽量优先制作的前提下,\(2\) 号菜肴尽量优先制作。

  3. 在满足所有限制,\(1\) 号和 \(2\) 号菜肴尽量优先的前提下,\(3\) 号菜肴尽量优先制作。

  4. 在满足所有限制,\(1\) 号和 \(2\) 号和 \(3\) 号菜肴尽量优先的前提下,\(4\) 号菜肴尽量优先制作。

  5. 以此类推。

例 1:共 \(4\) 道菜肴,两条限制 \((3,1)\)\((4,1)\),那么制作顺序是 \(3,4,1,2\)

例 2:共 \(5\) 道菜肴,两条限制 \((5,2)\)\((4,3)\),那么制作顺序是 \(1,5,2,4,3\)

例 1 里,首先考虑 \(1\),因为有限制 \((3,1)\)\((4,1)\),所以只有制作完 \(3\)\(4\) 后才能制作 \(1\),而根据 3,\(3\) 号又应尽量比 \(4\) 号优先,所以当前可确定前三道菜的制作顺序是 \(3,4,1\);接下来考虑 \(2\),确定最终的制作顺序是 \(3,4,1,2\)

\(2\) 里,首先制作 \(1\) 是不违背限制的;接下来考虑 \(2\) 时有 \((5,2)\) 的限制,所以接下来先制作 \(5\) 再制作 \(2\);接下来考虑 \(3\) 时有 \((4,3)\) 的限制,所以接下来先制作 \(4\) 再制作 \(3\),从而最终的顺序是 \(1,5,2,4,3\)。现在你需要求出这个最优的菜肴制作顺序。无解输出 Impossible!(首字母大写,其余字母小写)

输入格式

第一行是一个正整数 \(t\),表示数据组数。接下来是 \(t\) 组数据。对于每组数据:第一行两个用空格分开的正整数 \(n\)\(m\),分别表示菜肴数目和制作顺序限制的条目数。接下来 \(m\) 行,每行两个正整数 \(x,y\),表示 \(x\) 号菜肴必须先于 \(y\) 号菜肴制作的限制。

输出格式

输出文件仅包含 \(t\) 行,每行 \(n\) 个整数,表示最优的菜肴制作顺序,或者 Impossible! 表示无解。

输入输出样例 #1

输入 #1

3
5 4
5 4
5 3
4 2
3 2
3 3
1 2
2 3
3 1
5 2
5 2
4 3

输出 #1

1 5 3 4 2 
Impossible! 
1 5 2 4 3

说明/提示

【样例解释】

第二组数据同时要求菜肴 \(1\) 先于菜肴 \(2\) 制作,菜肴 \(2\) 先于菜肴 \(3\) 制作,菜肴 \(3\) 先于菜肴 \(1\) 制作,而这是无论如何也不可能满足的,从而导致无解。

【数据范围】

\(100\%\) 的数据满足 \(n,m\le 10^5\)\(1\le t\le 3\)

\(m\) 条限制中可能存在完全相同的限制。
这道题要用到反图和逆向思维,我们可以用优先队列先选最大的,然后再逆向输出

#include<iostream>
#include<vector>
#include<cstring>
#include<queue>
using namespace std;

const int N = 1e5 + 5;  // 定义最大菜肴数量
int inc[N];  // 入度数组,记录每个菜肴的入度
int ans[N];  // 存储最终的菜肴制作顺序
int n, m;    // n表示菜肴数量,m表示限制条件数量
vector<int> v[N];  // 邻接表,存储每个菜肴的后继菜肴

// 拓扑排序函数,返回是否成功找到合法的制作顺序
bool tuopu() {
    priority_queue<int> q;  // 使用最大堆,确保每次选择编号最大的菜肴
    for (int i = 1; i <= n; i++) {
        if (inc[i] == 0) q.push(i);  // 将所有入度为0的菜肴加入堆
    }
    int num = 0;  // 记录已经处理的菜肴数量
    while (!q.empty()) {
        int temp = ans[++num] = q.top();  // 取出堆顶元素,放入答案数组
        q.pop();
        for (int i = 0; i < v[temp].size(); i++) {  // 遍历当前菜肴的所有后继
            inc[v[temp][i]]--;  // 减少后继的入度
            if (inc[v[temp][i]] == 0) q.push(v[temp][i]);  // 如果入度为0,加入堆
        }
    }
    if (num == n) return true;  // 如果所有菜肴都被处理,返回true
    else return false;  // 否则返回false
}

// 处理每组数据的函数
void solve() {
    cin >> n >> m;
    for (int i = 1; i <= n; i++) v[i].clear();  // 清空邻接表
    memset(ans, 0, sizeof(ans));  // 清空答案数组
    memset(inc, 0, sizeof(inc));  // 清空入度数组
    int cnt = 0;
    while (m--) {
        int x, y;
        cin >> x >> y;
        v[y].push_back(x);  // 添加限制条件,y必须在x之前制作
        inc[x]++;  // 增加x的入度
    }
    if (tuopu()) {  // 如果拓扑排序成功
        for (int i = n; i >= 1; i--) cout << ans[i] << " ";  // 输出制作顺序
    } else {
        cout << "Impossible!";  // 否则输出无解
    }
    cout << endl;
}

int main() {
    int t;
    cin >> t;  // 输入数据组数
    while (t--) {
        solve();  // 处理每组数据
    }
    return 0;
}

posted @ 2025-03-08 13:38  郭轩均  阅读(66)  评论(0)    收藏  举报