题目描述
一家超市要每天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
题目分析
对于差分约束的题目,一开始是题目中的找不等式关系。
设num[i] //表示在i时刻申请的人数。 x[i] //表示在i时刻雇佣的人数
不等式关系为:
1.0<=x[i]<=num[i] //i时刻选择的人数要大于等于0,小于等于num[i]
2.x[i-7]+x[i-6]+……+x[i-1]+x[i]>=r[i] //能够在i时刻服务的人数,一定要大于等于i时刻需要的人数
然后我们会发现:2式不满足差分约束不等式的基本形式(k[i]>=k[j]+c)。但我们又可以发现:2式是一个连续数的和。因此我们可以用前缀和来优化一下该式(因为要用前缀和优化,因此我们需要空出0号点,所以我们可以将时间从0-23改为1-24)。
设s[i] //前i个时刻需要雇佣的人数(x[i]的前缀和)
优化之后的不等式关系为:
1.s[i]-s[i-1]>=0 -> s[i]>=s[i-1] //i时刻选择的人数要大于等于0
2.s[i]-s[i-1]<=num[i] -> s[i]-num[i]<=s[i-1] //i时刻选择的人数要小于等于num[i]
3.能够在i时刻服务的人数,一定要大于等于i时刻需要的人数。我们需要分段来看:
i>7时:s[i]-s[i-8]>=r[i] -> s[i]>=s[i-8]+r[i]
i<=7时:s[i]+s[24]-s[i+16]>=r[i] -> s[i]>=s[i+16]+r[i]-s[24]
此时,处理最后一个不等式之外,其它所有不等式都满足差分约束不等式的形式了。那么如何才能让该式符合条件呢?我们可以把s[24]看成一个常数,因为n的范围是[0-1000]。因此我们只需要枚举0-1000中的所有数,并将其作为s[24]的值,然后检查该值是否满足条件即可。s[24]为1-24时刻内需要的人数之和,我们要求的是所需人数的最小值,因此最小的满足条件的数即为正确答案。
4.因为我们将s[24]设为了一个定值,所以在建图时我们需要见两条边来体现一下这一点:s[24]=x => x<=s[24] && s[24]<=x
因为这道题求的是答案的最小值,因此边要设为:边(u,v,w)表示u+w<=v
,并且要求最长路。
再来看看这道题什么情况下无解:假设当前s[24]=x,如果此时用spfa求解答案时出现了正环,说明该情况不合法。如果枚举了s[24]所有可能的取值后都不合法,则此题无解。
补:因为我们是从小到大枚举s[24],要找出s[24]的最小值,具有单调性。因此我们其实是可以用二分得到s[24]的。
代码如下
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <map>
#include <queue>
#include <stack>
#include <vector>
#include <set>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
#define PII pair<int,int>
#define x first
#define y second
using namespace std;
const int N=30,M=105,INF=0x3f3f3f3f;
int h[N],e[M],w[M],ne[M],idx;
int dist[N],cnt[N];
int r[N],num[N];
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 s) //根据当前s[24]的值进行建图
{
idx=0; //先将图清空
memset(h,-1,sizeof h);
for(int i=1;i<=24;i++)
{
add(i-1,i,0); //不等式1
add(i,i-1,-num[i]); //不等式2
}
for(int i=8;i<=24;i++) add(i-8,i,r[i]); //不等式3的情况1
for(int i=0;i<8;i++) add(i+16,i,r[i]-s); //不等式3的情况2
add(0,24,s),add(24,0,-s); //不等式4
}
bool spfa(int s)
{
build(s);
memset(st,0,sizeof st);
memset(cnt,0,sizeof cnt);
memset(dist,-0x3f,sizeof dist);
queue<int> q;
q.push(0); //设一个虚拟源点0,做为起点
dist[0]=0;
st[0]=true;
while(q.size()) //标准的spfa模板,不多讲了
{
int u=q.front();
q.pop();
st[u]=false;
for(int i=h[u];~i;i=ne[i])
{
int v=e[i];
if(dist[v]<dist[u]+w[i])
{
dist[v]=dist[u]+w[i];
cnt[v]=cnt[u]+1;
if(cnt[v]>=25) return false; //加上0号点之后,一共用25个点
if(!st[v])
{
q.push(v);
st[v]=true;
}
}
}
}
return true;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
memset(num,0,sizeof num); //清空num[]数组
int n;
for(int i=1;i<=24;i++) scanf("%d",&r[i]);
scanf("%d",&n);
for(int i=1;i<=n;i++) //统计各个时刻申请的人数
{
int x;
scanf("%d",&x);
num[x+1]++;
}
int l=0,r=n+1; //通过二分计算s[24]的值
while(r>l)
{
int mid=l+r>>1;
if(spfa(mid)) r=mid; //如果当前mid值合法,让r=mid,看看能不能得到更小的解
else l=mid+1; //否则,让l=mid+1,加大mid
}
if(r<=n) printf("%d\n",r);
else puts("No Solution");
/* 枚举法计算s[24]的值
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;
}