【PAT甲级】1175 Professional Ability Test(30分)[dijkstra(优先队列),拓扑排序]

问题思考:

  1. 首先,若所有的计划(plan)中的节点都可达,则输出 Okay否则输出 Impossible。注意:这里的“plan”判断的是整个图(这里是有向图)上的节点,而不只是那K个queries节点。若存在环,则必然在经历一趟拓扑排序之后,还有留存节点未能遍历到,即判断环内有节点不可达。
  2. 其次,每个query对应输出最佳plan,要求S(score )最少的基础上多争取D(voucher)。这里的麻烦在于,如何化成一个单源最短路问题(这里显然可能存在多个 i 节点是没有前置要求的,即“You may take test i directly”的,即“多源”的最短路)?—— 一个技巧性的做法就是,额外设置一个起点(与所有 i 节点直接相连),并求该点到整个图上的节点的单源最短路问题。
  3. 并用一个Last数组在每选出一条最短路边的时候记录上个节点,用来到时候追溯回去,一直追溯到预设的起点。因为该起点和所有的 i 节点直接相连,所以,求所有 i 节点到query节点的最短路中的最短路,等价于求该起点到queiry节点的最短路。
  4. 若先输出Okay,则之后根据Last数组输出each query节点的最短路
  5. 若先输出Impossible,之后只需判断query是否直接可达无需前置要求,否则输出Error。

代码实现:

#include<iostream>
#include<vector>
#include<queue>
using namespace std;
int n, m, k, in[1005], in2[1005], f, last[1005];
struct node {
    int v, score, voucher;
    bool operator < (const node &x) const {
        if (score != x.score) return score > x.score;
        return voucher < x.voucher; 
    }
};
struct edge {
    int next, s, d;
};
vector<edge> E[1005]; // 邻接表的简洁表示
queue<int> que;
vector<pair<int, int>> dis(1002, {2e9, -1});
bool circle() {
    vector<int> S;
    while (que.size()) {
        int now = que.front();
        que.pop();
        S.push_back(now);
        for (auto x : E[now]) {
            in2[x.next]--;
            if (!in2[x.next]) que.push(x.next);
        }
    }
    return S.size() == n;
}
void dijkstra() {
    vector<int> vis(1005);
    priority_queue<node> Q;
    Q.push({1002, 0, 0});
    while (Q.size()) {
        node now = Q.top();
        Q.pop();
        if (vis[now.v]) continue;
        vis[now.v] = 1;
        dis[now.v].first = now.score;
        dis[now.v].second = now.voucher;
        for (auto x : E[now.v]) {
            if (vis[x.next]) continue;
            if (dis[x.next].first > dis[now.v].first + x.s || 
            ((dis[x.next].first == dis[now.v].first + x.s) 
            && (dis[x.next].second < dis[now.v].second + x.d))) {
                dis[x.next].first = dis[now.v].first + x.s;
                dis[x.next].second = dis[now.v].second + x.d;
                last[x.next] = now.v;
                Q.push({x.next, dis[x.next].first, dis[x.next].second});
            }
        }
    }
    return ;
}
int main() {
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        int a, b, s, d;
        scanf("%d%d%d%d", &a, &b, &s, &d);
        // 构建邻接表
        E[a].push_back(edge{b, s, d});
        in[b]++, in2[b]++;
    }
    // 寻找无前置要求的节点
    for (int i = 0; i < n; i++) {
        if (in[i] == 0) {
            E[1002].push_back({i, 0, 0}); // 起点与无前置要求的节点相连
            que.push(i);
        }
    }
    f = circle();
    dijkstra();
    cin >> k;
    if (f) cout << "Okay.\n";
    else cout << "Impossible.\n";
    for (int i = 0, q; i < k; i++) {
        scanf("%d", &q);
        if (!in[q]) cout << "You may take test " << q << " directly.\n";
        else if (!f) cout << "Error.\n";
        else {
            vector<int> path;
            while (q != 1002) {
                path.push_back(q);
                q = last[q];
            }
            for (int j = path.size() - 1; j >= 0; j--) {
                cout << path[j];
                j && cout << "->";
            }
            cout << '\n';
        }
    }
    return 0;
}

知识点:优先队列的小于号重载

  • 这里需要注意,重载小于号的方向,下面是一个实验样例。
#include<iostream>
#include<vector>
#include<queue>
struct node {
    int v, score, voucher;
    bool operator < (const node &x) const {
        if (score != x.score) return score > x.score;
        return voucher < x.voucher; 
    }
};
int main() {
    priority_queue<node> que;
    que.push(node{0, 1, 2});
    que.push(node{1, 1, 3});
    que.push(node{2, 2, 2});
    while (que.size()) {
        node now = que.top(); que.pop();
        cout << "now : " << now.v << " " << now.score << " " << now.voucher << endl;
    }
    return 0;
}
  • 运行结果: 
now : 1 1 3
now : 0 1 2
now : 2 2 2

可见如果想要让优先队列(内部结构为堆)满足按其某个property从小到大排序,就得反着写:

bool operator < (const node &x) const {
    return this.property > x.property
}

这跟 sort 又不一样,sort从小到大就是 < 号,用正着写就可以了。例如这道题:【PAT甲级】1179 Chemical Equation(30分)[dfs,搜索与回溯,排序]

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值