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()