雇佣收银员(差分约束)

题目描述

一家超市要每天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;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lwz_159

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值