雇佣收银员
题目描述
核心思路
这题需要用差分约束来求解,具体思路如下:
我们定义以下变量的含义:
num[i]
表示在 t i t_i ti时刻来应聘的人数x[i]
表示在 t i t_i ti时刻公司选择雇佣的人数R[i]
表示在 t i t_i ti时刻需要的员工的最小需求量S[i]
表示满足能在 t i t_i ti时刻上岗工作的公司已经聘用的员工总数。比如当 t i = 24 h t_i=24h ti=24h时, S i = 100 S_i=100 Si=100,则说明这100个人,它们的工作服务时间段是包括 t i t_i ti的,即在 t i t_i ti时刻他们在岗工作
根据以上定义,我们再来梳理一下不等式的关系:
-
0 ≤ x i ≤ n u m [ i ] 0\leq x_i\leq num[i] 0≤xi≤num[i],因为聘用的人数不可能大于来应该的人数,因此有 x i ≤ n u m [ i ] x_i\leq num[i] xi≤num[i],而且公司选择聘用的人数不可能为-1,要么都不聘用,因此有 x i ≥ 0 x_i\geq 0 xi≥0
-
x i − 7 + x i − 6 + x i − 5 + x i − 4 + x i − 3 + x i − 2 + x i − 1 + x i ≥ R i x_{i-7}+x_{i-6}+x_{i-5}+x_{i-4}+x_{i-3}+x_{i-2}+x_{i-1}+x_i\geq R_i xi−7+xi−6+xi−5+xi−4+xi−3+xi−2+xi−1+xi≥Ri,即公司聘用的能够在 t i t_i ti时刻处于工作服务状态的员工总数,应该 ≥ \geq ≥在 t i t_i ti时刻最小需要的员工总数
问题:如何理解 x i − 7 + x i − 6 + x i − 5 + x i − 4 + x i − 3 + x i − 2 + x i − 1 + x i x_{i-7}+x_{i-6}+x_{i-5}+x_{i-4}+x_{i-3}+x_{i-2}+x_{i-1}+x_i xi−7+xi−6+xi−5+xi−4+xi−3+xi−2+xi−1+xi这个式子呢?
由于题目说了,员工一定会连续工作8个小时,即每个员工的工作服务时常为 8 h 8h 8h。那么我们就想,哪些员工他们的工作服务时间段是包括 t i t_i ti这个时刻的呢?如果包括,则说明他们在 t i t_i ti这个时刻,一定可以上岗工作服务。因此 x i − 7 + x i − 6 + x i − 5 + x i − 4 + x i − 3 + x i − 2 + x i − 1 + x i x_{i-7}+x_{i-6}+x_{i-5}+x_{i-4}+x_{i-3}+x_{i-2}+x_{i-1}+x_i xi−7+xi−6+xi−5+xi−4+xi−3+xi−2+xi−1+xi就表示能在 t i t_i ti时刻上岗工作服务的员工总数,那么这个总数就应该要 ≥ t i \geq t_i ≥ti时刻需要的员工的最小需求量 R i R_i Ri。
x i − 7 + x i − 6 + x i − 5 + x i − 4 + x i − 3 + x i − 2 + x i − 1 + x i x_{i-7}+x_{i-6}+x_{i-5}+x_{i-4}+x_{i-3}+x_{i-2}+x_{i-1}+x_i xi−7+xi−6+xi−5+xi−4+xi−3+xi−2+xi−1+xi这个是连加的形式,那么很容易想到用前缀和来表示,即用 S [ i ] S[i] S[i]来表示,其中 1 ≤ i ≤ 24 1\leq i\leq 24 1≤i≤24。由于前缀和需要用到 S [ 0 ] S[0] S[0],因此我们需要把时间都向右平移一个单位,即现在时间为 1 , 2 , ⋯ , 24 1,2,\cdots,24 1,2,⋯,24而不是再是原来的 0 , 1 , 2 , ⋯ , 23 0,1,2,\cdots,23 0,1,2,⋯,23了。
那么则会由如下的不等式关系:
- 0 ≤ x i ≤ n u m [ i ] 0\leq x_i\leq num[i] 0≤xi≤num[i],用前缀和来代替,那么就是 0 ≤ S i − S i − 1 ≤ n u m [ i ] 0\leq S_i-S_{i-1}\leq num[i] 0≤Si−Si−1≤num[i]
- S i ≥ R i S_i\geq R_i Si≥Ri
由于题目要求的是变量的最小值,那么就需要跑单源最长路,求出最长距离。因此把不等式关系都改写为 x i ≥ x j + c x_i\geq x_j+c xi≥xj+c的形式:
- S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 Si≥Si−1+0
- S i − 1 ≥ S i − n u m [ i ] S_{i-1}\geq S_i-num[i] Si−1≥Si−num[i]
对于 S i ≥ R i S_i\geq R_i Si≥Ri要分类讨论,因为时间它是循环的,当到了24之后就会变为0,又重新开始了。
-
当 i ≥ 8 i\geq 8 i≥8时,则有 S i − S i − 8 ≥ R i S_i-S_{i-8}\geq R_i Si−Si−8≥Ri,分析如下:
-
当 0 < i ≤ 7 0<i\leq 7 0<i≤7时,则要分为两部分了。一部分是 1 1 1到 7 7 7这个时间段,由于不足8h,因此说明需要向 24 24 24(包括它)之前的借一些时间,这主要就是因为时间是循环的。那么会得到 S i + ( S 24 − S i + 16 ) ≥ R [ i ] S_i+(S_{24}-S_{i+16})\geq R[i] Si+(S24−Si+16)≥R[i]分析如下:
进一步梳理不等式关系:
- S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 Si≥Si−1+0
- S i − 1 ≥ S i − n u m [ i ] S_{i-1}\geq S_i-num[i] Si−1≥Si−num[i]
- 当 i ≥ 8 i\geq 8 i≥8时, S i ≥ S i − 8 + R [ i ] S_i\geq S_{i-8}+R[i] Si≥Si−8+R[i]
- 当 0 < i ≤ 7 0<i\leq 7 0<i≤7时, S i ≥ S i + 16 − S 24 + R [ i ] S_i\geq S_{i+16}-S_{24}+R[i] Si≥Si+16−S24+R[i]
但是我们发现第四个约束条件中,有三个变量,平时我们都是遇到两个变量。其实,我们可以把 S 24 S_{24} S24当作常量。那么如何把它作为常量呢?由于题目说要求最少需要雇佣多少名收银员,那么其实也就是说要求出 S 24 S_{24} S24是多少。由于题目说了 0 ≤ N ≤ 1000 0\leq N\leq 1000 0≤N≤1000,因此最坏情况下是至少聘用了 1000 1000 1000个人。那么我们可以依次从0开始枚举到1000,我们把枚举的这个值 i i i其实就是 S 24 S_{24} S24,每次枚举一个 i i i,都去跑一下spfa,当第一次枚举到某个值时,它满足所有的约束条件,那么这个值就是我们要求的最小的 S 24 S_{24} S24,找到第一个就直接break。
注意这里当我们枚举到某个 S 24 = c S_{24}=c S24=c时,也要把它写成差分约束的形式,即 S 24 ≥ c S_{24}\geq c S24≥c和 S 24 ≤ c S_{24}\leq c S24≤c,建立一个超级源点 S 0 S_0 S0(为0),那么有 S 24 ≥ S 0 + c S_{24}\geq S_0+c S24≥S0+c和 S 0 ≥ S 24 − c S_{0}\geq S_{24}-c S0≥S24−c
因此,总结一下,总共有以下的约束条件:
- S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 Si≥Si−1+0
- S i − 1 ≥ S i − n u m [ i ] S_{i-1}\geq S_i-num[i] Si−1≥Si−num[i]
- 当 i ≥ 8 i\geq 8 i≥8时, S i ≥ S i − 8 + R [ i ] S_i\geq S_{i-8}+R[i] Si≥Si−8+R[i]
- 当 0 < i ≤ 7 0<i\leq 7 0<i≤7时, S i ≥ S i + 16 − S 24 + R [ i ] S_i\geq S_{i+16}-S_{24}+R[i] Si≥Si+16−S24+R[i]
- S 24 ≥ S 0 + c S_{24}\geq S_0+c S24≥S0+c
- S 0 ≥ S 24 − c S_{0}\geq S_{24}-c S0≥S24−c
由第一个约束条件 S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 Si≥Si−1+0可知,设立一个超级源点0号节点,则可以从超级源点出发,0->1->2-> ⋯ \cdots ⋯-> 24 24 24,因此可以到达所有节点,那么一定可以到达所有边。所以,这里满足了 “从源点出发,能遍历到所有边”这个条件,因此可以用差分约束。
代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=30,M=100,INF=0x3f3f3f3f;
int n;
int h[N],e[M],ne[M],w[M],idx;
//R[i]表示i时刻公司需要雇佣的最小的员工的需求量
//num[i]表示i时刻来应聘的员工的数量
int R[N],num[N];
//dist[i]表示i节点到起点的最长距离
int dist[N];
//循环队列q来存储入队节点
int q[N];
//cnt[i]表示从起点到达节点i一共经过了多少条边
int cnt[N];
//判断某个节点是否已经加入了q队列中
bool st[N];
//建图函数
void add(int a,int b,int c)
{
e[idx]=b;
w[idx]=c;
ne[idx]=h[a];
h[a]=idx++;
}
//将约束条件建成图
void build(int c)
{
//初始化表头
memset(h,-1,sizeof h);
idx=0;
//S[24]=c,写成了两个约束条件
add(0,24,c);
add(24,0,-c);
//当0<i<=7时,给这个约束条件建图
for(int i=1;i<=7;i++)
add(i+16,i,R[i]-c);
//当i>=8时,给这个约束条件建图
for(int i=8;i<=24;i++)
add(i-8,i,R[i]);
//给S[i]>=S[i-1]+0和S[i-1]>=S[i]-num[i]建图
for(int i=1;i<=24;i++)
{
add(i-1,i,0);
add(i,i-1,-num[i]);
}
}
//spfa算法用来 判断是否存在正环 和 求出最长距离
bool spfa(int c)
{
//对于枚举的每一个S[24]都去建图
build(c);
//初始化
memset(dist,-0x3f,sizeof dist);
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
int hh=0,tt=1;
//由于真正地建立了超级源点S[0]
dist[0]=0;
q[0]=0;
st[0]=true;
while(hh!=tt)
{
int t=q[hh++];
if(hh==N)
hh=0;
st[t]=false;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
//本来1~24共有24个节点,但是建立了1个超级源点,因此总共25个节点
//cnt[j]>=25说明从超级源点到节点j经过了25条边,那么则有26个节点
//这与实际的25个节点不符合,因此存在正环
if(cnt[j]>=25)
return false;
if(!st[j])
{
q[tt++]=j;
if(tt==N)
tt=0;
st[j]=true;
}
}
}
}
//说明不存在正环 且找到了最小的满足所有约束条件的S[24]
return true;
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
for(int i=1;i<=24;i++)
scanf("%d",&R[i]);
scanf("%d",&n);
memset(num,0,sizeof num);
while(n--)
{
int t;
scanf("%d",&t);
//由于前缀和S[0]占用0这个位置,因此我们让时间都向右平移一位
t++;
num[t]++;
}
//用来判断是否有解
bool flag=false;
for(int i=0;i<=1000;i++)
{
//枚举找到最小的满足所有约束条件的S[24]
if(spfa(i))
{
printf("%d\n",i);
flag=true;
break;
}
}
//说明无解
if(!flag)
puts("No Solution");
}
return 0;
}