雇佣收银员

雇佣收银员


题目描述

image-20210710201724728


核心思路

这题需要用差分约束来求解,具体思路如下:

我们定义以下变量的含义:

  • 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] 0xinum[i],因为聘用的人数不可能大于来应该的人数,因此有 x i ≤ n u m [ i ] x_i\leq num[i] xinum[i],而且公司选择聘用的人数不可能为-1,要么都不聘用,因此有 x i ≥ 0 x_i\geq 0 xi0

  • 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 xi7+xi6+xi5+xi4+xi3+xi2+xi1+xiRi,即公司聘用的能够在 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 xi7+xi6+xi5+xi4+xi3+xi2+xi1+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 xi7+xi6+xi5+xi4+xi3+xi2+xi1+xi就表示能在 t i t_i ti时刻上岗工作服务的员工总数,那么这个总数就应该要 ≥ t i \geq t_i ti时刻需要的员工的最小需求量 R i R_i Ri

    image-20210710211017955

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 xi7+xi6+xi5+xi4+xi3+xi2+xi1+xi这个是连加的形式,那么很容易想到用前缀和来表示,即用 S [ i ] S[i] S[i]来表示,其中 1 ≤ i ≤ 24 1\leq i\leq 24 1i24。由于前缀和需要用到 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] 0xinum[i],用前缀和来代替,那么就是 0 ≤ S i − S i − 1 ≤ n u m [ i ] 0\leq S_i-S_{i-1}\leq num[i] 0SiSi1num[i]
  • S i ≥ R i S_i\geq R_i SiRi

由于题目要求的是变量的最小值,那么就需要跑单源最长路,求出最长距离。因此把不等式关系都改写为 x i ≥ x j + c x_i\geq x_j+c xixj+c的形式:

  • S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 SiSi1+0
  • S i − 1 ≥ S i − n u m [ i ] S_{i-1}\geq S_i-num[i] Si1Sinum[i]

对于 S i ≥ R i S_i\geq R_i SiRi要分类讨论,因为时间它是循环的,当到了24之后就会变为0,又重新开始了。

  • i ≥ 8 i\geq 8 i8时,则有 S i − S i − 8 ≥ R i S_i-S_{i-8}\geq R_i SiSi8Ri,分析如下:

    image-20210710213044097

  • 0 < i ≤ 7 0<i\leq 7 0<i7时,则要分为两部分了。一部分是 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+(S24Si+16)R[i]分析如下:

    image-20210710214002246

进一步梳理不等式关系:

  • S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 SiSi1+0
  • S i − 1 ≥ S i − n u m [ i ] S_{i-1}\geq S_i-num[i] Si1Sinum[i]
  • i ≥ 8 i\geq 8 i8时, S i ≥ S i − 8 + R [ i ] S_i\geq S_{i-8}+R[i] SiSi8+R[i]
  • 0 < i ≤ 7 0<i\leq 7 0<i7时, S i ≥ S i + 16 − S 24 + R [ i ] S_i\geq S_{i+16}-S_{24}+R[i] SiSi+16S24+R[i]

但是我们发现第四个约束条件中,有三个变量,平时我们都是遇到两个变量。其实,我们可以把 S 24 S_{24} S24当作常量。那么如何把它作为常量呢?由于题目说要求最少需要雇佣多少名收银员,那么其实也就是说要求出 S 24 S_{24} S24是多少。由于题目说了 0 ≤ N ≤ 1000 0\leq N\leq 1000 0N1000,因此最坏情况下是至少聘用了 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 S24c S 24 ≤ c S_{24}\leq c S24c,建立一个超级源点 S 0 S_0 S0(为0),那么有 S 24 ≥ S 0 + c S_{24}\geq S_0+c S24S0+c S 0 ≥ S 24 − c S_{0}\geq S_{24}-c S0S24c

因此,总结一下,总共有以下的约束条件:

  • S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 SiSi1+0
  • S i − 1 ≥ S i − n u m [ i ] S_{i-1}\geq S_i-num[i] Si1Sinum[i]
  • i ≥ 8 i\geq 8 i8时, S i ≥ S i − 8 + R [ i ] S_i\geq S_{i-8}+R[i] SiSi8+R[i]
  • 0 < i ≤ 7 0<i\leq 7 0<i7时, S i ≥ S i + 16 − S 24 + R [ i ] S_i\geq S_{i+16}-S_{24}+R[i] SiSi+16S24+R[i]
  • S 24 ≥ S 0 + c S_{24}\geq S_0+c S24S0+c
  • S 0 ≥ S 24 − c S_{0}\geq S_{24}-c S0S24c

由第一个约束条件 S i ≥ S i − 1 + 0 S_i\geq S_{i-1}+0 SiSi1+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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卷心菜不卷Iris

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

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

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

打赏作者

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

抵扣说明:

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

余额充值