• AcWing 393 雇佣收银员


    题目传送门

    一、题目分析

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

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

    • \(x_0,x_1,x_2....x_{23}\) 表示从\(i\)点来的人中选择的人数

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

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

    \(x_i-7 + x_i-6 + x_i-5 + ... +x_i >= r_i,0 <= i <= 23\)
    每一个时刻\(i\)都需要满足对应的收银员的数目。因为每个员工工作时间最长是\(8\)小时,那么如果在\(i\)这个时刻他还在工作岗位上,那么他一定是在最近\(8\)个小时内上岗的,即\(x_i-7,x_i-6,...,x_i\)时上岗。

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

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

    • \(S_i - S_{i-8} >= r_i,i >= 8\)
    • \(S_i + S_{24} - S_{i+16} >= r_i,0 < i < 8\)
      可以找一下规律,凑够八段就行

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

    • \(S_i >= S_{i-1} + 0\)

    • \(S_{i-1} >= S_i - num[i]\)

    • \(S_i >= S_{i-8} + r_i,8 <= i<= 24\)

    • \(S_i >= S_{i+16} - S_{24} + r_i,0 < i < 8\)

    但是这里对于第四个式子可以发现有三个变量,其中\(S_{24}\)也属于一个变量,对于这种问题一般枚举\(S_{24}\)的范围,这样\(S_{24}\)就相当于是一个常量了,由题目可知\(N\)最多为\(1000\),所以枚举\(0 \sim 1000\)即可,当\(S_{24}\)固定之后那么需要在建图的时候体现这个限制,也即\(S_{24} = c <==> S_{24} >= c\) \(and\) \(S_{24} <= c\),即\(S_{24} >= S_0 + c\) \(and\) \(S_0 >= S_{24} - c\),在建图的时候不等号右边的节点往左边的节点连一条权重为\(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;
    }
    
  • 相关阅读:
    QuickSort
    Java细节
    Java
    Prime
    apache和tomcat有什么不同,为什么要整合apache 和tomcat?
    java线程面试题及答案
    Struts2中的ModelDriven机制及其运用
    Java中instanceof关键字的用法总结
    spring特点与好处
    spring与struts有什么区别?
  • 原文地址:https://www.cnblogs.com/littlehb/p/16066377.html
Copyright © 2020-2023  润新知