393 雇佣收银员(差分约束--扩展(存在三个变量的时候枚举其中一个变量的范围))

1. 问题描述:

一家超市要每天 24 小时营业,为了满足营业需求,需要雇佣一大批收银员。已知不同时间段需要的收银员数量不同,为了能够雇佣尽可能少的人员,从而减少成本,这家超市的经理请你来帮忙出谋划策。经理为你提供了一个各个时间段收银员最小需求数量的清单 R(0),R(1),R(2),…,R(23)。R(0) 表示午夜 00:00 到凌晨 01:00 的最小需求数量,R(1) 表示凌晨 01:00 到凌晨 02:00 的最小需求数量,以此类推。一共有 N 个合格的申请人申请岗位,第 i 个申请人可以从 ti 时刻开始连续工作 8 小时。收银员之间不存在替换,一定会完整地工作 8 小时,收银台的数量一定足够。现在给定你收银员的需求清单,请你计算最少需要雇佣多少名收银员。

输入格式

第一行包含一个不超过 20 的整数,表示测试数据的组数。对于每组测试数据,第一行包含 24 个整数,分别表示 R(0),R(1),R(2),…,R(23)。第二行包含整数 N。接下来 N 行,每行包含一个整数 ti。

输出格式

每组数据输出一个结果,每个结果占一行。如果没有满足需求的安排,输出 No Solution。

数据范围

0 ≤ R(0) ≤ 1000,
0 ≤ N ≤ 1000,
0 ≤ ti ≤ 23

输入样例:

1
1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
5
0
23
22
1
10

输出样例:

1
来源:https://www.acwing.com/problem/content/description/395/

2. 思路分析:

如果不说明这道题目可以差分约束来解决,根本想不到呀,所以看出来是差分约束的题目很关键。因为求解的是最小值所以需要使用单源最长路径来求解,对于差分约束的题目难点在于找全题目中涉及到的不等式关系,我们使用x0,x1,x2....x23表示从i点来的人中挑选的人数,num[0],num[1],num[2]...num[23]记录i时刻来的人数,根据题目的描述我们可以得到下面的不等式关系:

① 0 <= xi <= num[i],0 <= i <= 23;

② 每一个时刻i都需要满足对应的收银员的数目,也即:xi-7 + xi-6 + xi-5 + ... xi >= ri,0 <= i <= 23;

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

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

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

  • Si >= Si-1 + 0,==> i-1向i连一条权重为0的边
  • Si-1 >= Si - num[i],i向i - 1连一条-num[i]的边
  • Si >= Si-8 + ri,8 <= i<= 24,i-8向i连一条权重为ri的边 
  • Si >= Si+16 - S24 + ri,0 < i < 8

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

3. 代码如下:

import collections
from typing import List


class Solution:
    # spfa的建图
    def build(self, c: int, r: List[int], num: List[int], g: List[List[int]]):
        # 因为枚举的是S24所以这里需要体现24这条边, 也即S24 = c这条边==> S24 >= c并且 S24 <= c
        # 与0号点建立起关系
        g[0].append((24, c))
        g[24].append((0, -c))
        # 分情况建边
        for i in range(1, 25):
            g[i - 1].append((i, 0))
            g[i].append((i - 1, -num[i]))
        for i in range(8, 25):
            g[i - 8].append((i, r[i]))
        for i in range(1, 8):
            g[i + 16].append((i, r[i] - c))
    
    # spfa求最长路
    def spfa(self, c: int, r: List[int], num: List[int]):
        g = [list() for i in range(30)]
        # 建图
        self.build(c, r, num, g)
        INF = -10 ** 10
        # 总共有25个点
        dis = [INF] * 25
        dis[0] = 0
        q = collections.deque()
        q.append(0)
        vis = [0] * 25
        vis[0] = 1
        # count列表记录最长路的边数
        count = [0] * 25
        while q:
            p = q.popleft()
            vis[p] = 0
            for next in g[p]:
                if dis[next[0]] < dis[p] + next[1]:
                    dis[next[0]] = dis[p] + next[1]
                    # 使用count记录最长路的边数, 如果到某个点的最长路大于了25说明存在正环
                    count[next[0]] = count[p] + 1
                    if count[next[0]] >= 25: return -1
                    if vis[next[0]] == 0:
                        q.append(next[0])
                        vis[next[0]] = 1
        # dis[24]就是答案
        return dis[24]

    def process(self):
        # T组测试数据
        T = int(input())
        while T:
            r = [0] + list(map(int, input().split()))
            n = int(input())
            num = [0] * 25
            for i in range(n):
                t = int(input())
                # 往后移动一位这样可以使得0这个位置空出来
                t += 1
                # 计算每一个时刻来的人数
                num[t] += 1
            res = -1
            for i in range(1001):
                # 枚举S24变量的值这样可以使得S24看成是一个常量, 当枚举范围内的所有数字还无解说明真的无解
                res = self.spfa(i, r, num)
                if res != -1:
                    break
            # 存在负环说明无解
            if res == -1:
                print("No Solution")
            else:
                print(res)
            T -= 1


if __name__ == "__main__":
    Solution().process()

猜你喜欢

转载自blog.csdn.net/qq_39445165/article/details/121184755