AcWing 393 雇佣收银员

题目传送门

一、题目分析

因为求解的是最小值所以需要使用单源最长路径来求解,对于差分约束的题目难点在于找全题目中涉及到的不等式关系,我们使用:

  • num[0]num[1]num[2]...num[23] 表示i点可以来工作的人数

  • x0x1x2....x23 表示从i点来的人中选择的人数

根据题目的描述可以得到下面的不等式关系:

0<=xi<=num[i]0<=i<=23
每个时间点i,选中的人数,必然小于等于可选的人数。

xi7+xi6+xi5+...+xi>=ri0<=i<=23
每一个时刻i都需要满足对应的收银员的数目。因为每个员工工作时间最长是8小时,那么如果在i这个时刻他还在工作岗位上,那么他一定是在最近8个小时内上岗的,即xi7,xi6,...,xi时上岗。

对于②不是差分约束的标准形式,但是可以发现其实加的是一整段的和所以我们考虑前缀和来解决,前缀和就需要考虑将0这个位置空出来,所以将所有的位置都往后移动一位,使用Si表示x1+x2+...xi,其中S0=0,我们可以使用关于S的表达式来表示①②:
对于①可以得到:
0<=SiSi1<=num[i]1<=i<=24

对于②因为是连续工作八小时所以需要分段来看,我们以8作为分界线分为两段:

  • SiSi8>=ri,i>=8
  • Si+S24Si+16>=ri,0<i<8
    可以找一下规律,凑够八段就行

因为求解的是最小值所以使用最长路径来求解,也即需要将不等式整理成xi>=xj+ck的形式,整理一下上面的不等式得到:

  • Si>=Si1+0

  • Si1>=Sinum[i]

  • Si>=Si8+ri8<=i<=24

  • Si>=Si+16S24+ri0<i<8

但是这里对于第四个式子可以发现有三个变量,其中S24也属于一个变量,对于这种问题一般枚举S24的范围,这样S24就相当于是一个常量了,由题目可知N最多为1000,所以枚举01000即可,当S24固定之后那么需要在建图的时候体现这个限制,也即S24=c<==>S24>=c and S24<=c,即S24>=S0+c and S0>=S24c,在建图的时候不等号右边的节点往左边的节点连一条权重为c的边即可。

二、枚举

#include <bits/stdc++.h>
using namespace std;
const int N = 30, M = 100;
int n;       // n个合格的申请人申请岗位
int r[N];    //各个时间段需要的人员数量
int num[N];  //第i个申请人可以从num[i]时刻开始连续工作8小时
int dist[N]; //最长距离,本题是求“最少需要雇佣”,所以是最长路
int cnt[N];  //用于判正环(最长路)
bool st[N];  // spfa专用是否在队列中的标识
//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
//建图
void build(int c) {
    //每次清空邻接表
    memset(h, -1, sizeof h);
    idx = 0;

    // s(i):从1点到i点,需要雇佣的人员数量
    for (int i = 1; i <= 24; i++) {
        add(i - 1, i, 0);       // s(i)   >= s(i-1) + 0
        add(i, i - 1, -num[i]); // s(i-1) >= s(i)-num[i]
    }
    // s(i) >= s(i-8) + r(i)
    for (int i = 8; i <= 24; i++) add(i - 8, i, r[i]);

    // s(i)>=s(i+16)−s(24)+r(i)
    for (int i = 1; i <= 7; i++) add(i + 16, i, -c + r[i]);
    // s24的引入,需要再加两个不等式  s(24)=c

    add(0, 24, c);
    // s(24)>=c -> s(24) >= c +s(0)
    // -> s(24) >= s(0) + c

    add(24, 0, -c);
    // s(24)<=c -> s(24) <= c +s(0)
    // -> s(0) >= s(24) -c
}

// spfa找正环
bool spfa(int c) {
    build(c); //建图
    //每次初始化
    memset(st, 0, sizeof st);
    memset(cnt, 0, sizeof cnt);
    memset(dist, -0x3f, sizeof dist);
    queue<int> q;
    //超级源点大法好~
    for (int i = 0; i <= 24; i++) {
        q.push(i);
        st[i] = true;
    }
    while (q.size()) {
        int t = q.front();
        q.pop();
        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i]) {
            int u = e[i];
            if (dist[u] < dist[t] + w[i]) { //最长路
                dist[u] = dist[t] + w[i];
                cnt[u] = cnt[t] + 1;
                // 一共25个点,发现正环了则返回false
                if (cnt[u] >= 25) return false;
                if (!st[u]) {
                    q.push(u);
                    st[u] = true;
                }
            }
        }
    }
    return true;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        //各个时间段收银员最小需求数量的清单
        //这里为了使用前缀和,向后进行了错一位操作
        for (int i = 1; i <= 24; i++) cin >> r[i];
        cin >> n; // n个合格的申请人申请岗位

        memset(num, 0, sizeof num); //多组测试数据,所以需要每次清零
        for (int i = 0; i < n; i++) {
            int t;
            cin >> t;
            // 申请人可以从num[t+1]时刻开始连续工作8小时
            num[t + 1]++; //++代表这个时段可以干活的人数+1
        }
        //枚举0~1000所有点,找到最小的
        bool success = false;
        for (int i = 0; i <= 1000; i++)
            if (spfa(i)) {
                cout << i << endl;
                success = true;
                break;
            }
        if (!success) puts("No Solution");
    }
    return 0;
}

三、二分

#include <bits/stdc++.h>
using namespace std;
const int N = 30, M = 100;
int n;       // n个合格的申请人申请岗位
int r[N];    //各个时间段需要的人员数量
int num[N];  //第i个申请人可以从num[i]时刻开始连续工作8小时
int dist[N]; //最长距离,本题是求“最少需要雇佣”,所以是最长路
int cnt[N];  //用于判正环(最长路)
bool st[N];  // spfa专用是否在队列中的标识
//邻接表
int e[M], h[N], idx, w[M], ne[M];
void add(int a, int b, int c) {
    e[idx] = b, ne[idx] = h[a], w[idx] = c, h[a] = idx++;
}
//建图
void build(int c) {
    //每次清空邻接表
    memset(h, -1, sizeof h);
    idx = 0;

    // s(i):从1点到i点,需要雇佣的人员数量
    for (int i = 1; i <= 24; i++) {
        add(i - 1, i, 0);       // s(i)   >= s(i-1) + 0
        add(i, i - 1, -num[i]); // s(i-1) >= s(i)-num[i]
    }
    // s(i) >= s(i-8) + r(i)
    for (int i = 8; i <= 24; i++) add(i - 8, i, r[i]);

    // s(i)>=s(i+16)−s(24)+r(i)
    for (int i = 1; i <= 7; i++) add(i + 16, i, -c + r[i]);
    // s24的引入,需要再加两个不等式  s(24)=c

    add(0, 24, c);
    // s(24)>=c -> s(24) >= c +s(0)
    // -> s(24) >= s(0) + c

    add(24, 0, -c);
    // s(24)<=c -> s(24) <= c +s(0)
    // -> s(0) >= s(24) -c
}

// spfa找正环
bool spfa(int c) {
    build(c); //建图
    //每次初始化
    memset(st, 0, sizeof st);
    memset(cnt, 0, sizeof cnt);
    memset(dist, -0x3f, sizeof dist);
    queue<int> q;
    //超级源点大法好~
    for (int i = 0; i <= 24; i++) {
        q.push(i);
        st[i] = true;
    }
    while (q.size()) {
        int t = q.front();
        q.pop();
        st[t] = false;
        for (int i = h[t]; ~i; i = ne[i]) {
            int u = e[i];
            if (dist[u] < dist[t] + w[i]) { //最长路
                dist[u] = dist[t] + w[i];
                cnt[u] = cnt[t] + 1;
                // 一共25个点,发现正环了则返回false
                if (cnt[u] >= 25) return false;
                if (!st[u]) {
                    q.push(u);
                    st[u] = true;
                }
            }
        }
    }
    return true;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        //各个时间段收银员最小需求数量的清单
        //这里为了使用前缀和,向后进行了错一位操作
        for (int i = 1; i <= 24; i++) cin >> r[i];
        cin >> n; // n个合格的申请人申请岗位

        memset(num, 0, sizeof num); //多组测试数据,所以需要每次清零
        for (int i = 0; i < n; i++) {
            int t;
            cin >> t;
            // 申请人可以从num[t+1]时刻开始连续工作8小时
            num[t + 1]++; //++代表这个时段可以干活的人数+1
        }

        // 二分总人数
        int l = 0, r = n;
        //雇佣的人员,最少是0,最多是1000
        //人员雇佣的越多,肯定越能满足用工要求,但成本会高
        //所以,存在单调性,可以二分
        while (l < r) {
            int mid = l + (r - l >> 1); //这么写是为了防止溢出
            if (spfa(mid))              //如果不等式组有解,向左逼近
                r = mid;
            else
                l = mid + 1; //无解向右逼近
        }
        if (!spfa(l)) //如果最终计算出来的结果还是无解,那就是无解
            puts("No Solution");
        else
            cout << l << endl; //输出最小值
    }
    return 0;
}
posted @   糖豆爸爸  阅读(76)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2020-03-28 任务调度的解决办法
2014-03-28 在sphinx中应用复杂过滤条件
2014-03-28 CentOS6.9下安装MariaDB10.2.11
Live2D
点击右上角即可分享
微信分享提示