2023山东ICPC省赛Problem B.建筑公司(拓扑排序)

2023 山东 I C P C 省赛 P r o b l e m B . 建筑公司 \Huge{2023山东ICPC省赛Problem B.建筑公司} 2023山东ICPC省赛ProblemB.建筑公司

文章目录


比赛链接:Dashboard - The 13th Shandong ICPC Provincial Collegiate Programming Contest - Codeforces

官方题解:B - 建筑公司 - SUA Wiki

题意

题目给出若干中工种和每类工种的人数,然后给出若干项工程,每项工程的性质有:

  1. 完成该项工程所需的各工种人数。
  2. 完成该项工程后可以增加的每类工种人数。要求求出最多能够完成多少项工程?

每项工程需要完成的时间忽略不计,可以理解为只要各工种人数满足该工程所需人数,则和获取此工程的增加人数,并且需要完成该工程的人数不会消耗

思路

我们可以简化题目,看作每项工程只需一类工种,那么自然很容易想到拓扑排序

但是这道题稍微复杂一些,每项工程需要的工种种类多一些。

我们考虑对于每项工程建立拓扑图,并且把每项工程所需的各工种人数看作是连接在工程上的节点,若现有工种人数大于该工程的所需人数,则将该工种所代表的节点从该工程上去掉;若某工程的入度 0 0 0,则表示该工程所有需要的工人数量都满足,那么就可以完成该工程,并且将该工程可增加的工种人数加上。

需要注意的是:本题的重点是如何删除每个工程上的节点(工程需要的工种人数)?

  • 可行的做法是:可以将每个工种数量不满足的工程给存起来然后排序。
  • 具体排序规则为:将需要该工种的工程根据需要的数量从小到大排序。
  • 容易知道,若当前度不为 0 0 0的节点,只有其工种人数增加后,该节点才可能被消除。

注意点已详细注释在标程代码中,以便理解。

标程

#include<bits/stdc++.h>

using namespace std;

#define IOS ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
#define LL long long 
#define ULL unsigned long long 
#define PII pair<int, int>
#define lowbit(x) (x & -x)
#define Mid ((l + r) >> 1)
#define ALL(x) x.begin(), x.end()
#define endl '\n'
#define fi first 
#define se second

const int INF = 0x7fffffff;
const int Mod = 1e9 + 7;
const int N = 2e5 + 10;

int g, n, k, m, t, u;
map<int, int> a;//a存每个工种的人数
map<int, priority_queue<PII, vector<PII>, greater<PII>>> q;//存每个工种数量不满足的工程
vector<PII> v[N];//v存每个工程可增加的每个工种人数
vector<int> c(N);//c存每个工程缺的工种数量
queue<int> qu;//存当前入度为0的工程

void Solved() {
    cin >> g;
    for(int i = 1; i <= g; i ++ ) {//记录各工种人数
        cin >> t >> u;
        a[t] = u;
    }
    cin >> n;
    for(int i = 1; i <= n; i ++ ) {
        cin >> m;
        for(int j = 1; j <= m; j ++ ) {//每项工程需要人数
            cin >> t >> u;
            if(a[t] < u) {             //只保存还需要的人数就行(找出初始入度为0的工程)
                c[i] ++;
                q[t].push({u, i});
            }
        }
        cin >> k;
        for(int j = 1; j <= k; j ++ ) {//每项工程增加人数
            cin >> t >> u;
            v[i].push_back({t, u});
        }
    }
    for(int i = 1; i <= n; i ++ ) {//先将入度为0的工程处理掉
        if(!c[i]) qu.push(i);
    }
    int res = 0;
    //q[i]表示对于第i种工人人数,不满足哪些工程
    //qu中存放当前入度为0的工程
    while(!qu.empty()) {
        int i = qu.front(); qu.pop();
        res ++;
        //对于每个工种,只有其数量增加,才能完成其他工程,所以只需要判断增加的工种即可
        for(auto f : v[i]) {
            t = f.fi, u = f.se;
            a[t] += u;            //添加当前工程可增加的各工种人数
            while(!q[t].empty()) {//判断当前所有需要工种t的工程
                PII p = q[t].top();
                if(a[t] >= p.fi) {
                    c[p.se] --;
                    q[t].pop();
                    //若当前工程需要的人都满足,则该工程入度为0
                    if(c[p.se] == 0) qu.push(p.se);
                } else {//对于当前工种,若工程不满足,则先跳过
                    break;
                }
            }
        }
    }
 
    cout << res << endl;
}

signed main(void) {
    IOS

    int ALL = 1; 
    // cin >> ALL;
    while(ALL -- ) Solved();
    // cout << fixed;//强制以小数形式显示
    // cout << setprecision(n); //保留n位小数

    return 0;
}
  • 35
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值