差分约束 - Cashier Employment - POJ 1275

差分约束 - Cashier Employment - POJ 1275

题意:

一家超市要每天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 ≤ t i ≤ 23 0≤R(0)≤1000, 0≤N≤1000, 0≤t_i≤23 0R(0)1000,0N1000,0ti23

输入样例:

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

分析:

本 题 要 求 最 小 值 , 跑 最 长 路 。 本题要求最小值,跑最长路。

r i 表 示 i 时 刻 需 要 多 少 收 营 员 , n u m i 表 示 i 时 刻 有 多 少 岗 位 可 供 申 请 。 r_i表示i时刻需要多少收营员,num_i表示i时刻有多少岗位可供申请。 riinumii

在 某 段 区 间 内 计 数 , 考 虑 前 缀 和 的 思 想 。 因 此 我 们 先 将 所 有 区 间 增 加 一 个 长 度 单 位 。 在某段区间内计数,考虑前缀和的思想。因此我们先将所有区间增加一个长度单位。

距 离 数 组 d i s [ i ] 表 示 [ 1 , i ] 这 段 时 间 内 最 多 可 以 雇 佣 的 收 营 员 数 量 。 距离数组dis[i]表示[1,i]这段时间内最多可以雇佣的收营员数量。 dis[i][1,i]

则 有 约 束 不 等 式 组 : 则有约束不等式组:

{ 0 ≤ d i s [ i ] − d i s [ i − 1 ] ≤ n u m i , 即 i 时 刻 能 雇 佣 的 收 营 员 数 量 。 < = > d i s [ i ] ≥ d i s [ i − 1 ] 和 d i s [ i − 1 ] ≥ d i s [ i ] − n u m i d i s [ i ] − d i s [ i − 8 ] ≥ r i , 8 ≤ i ≤ 24 , 即 i 时 刻 在 岗 收 营 员 数 量 不 少 于 r i 。 < = > d i s [ i ] ≥ d i s [ i − 8 ] + r i d i s [ i ] + d i s [ 24 ] − d i s [ i + 16 ] ≥ r i , 1 ≤ i ≤ 7 , 同 上 。 < = > d i s [ i ] ≥ d i s [ i + 16 ] − d i s [ 24 ] + r i \begin{cases}0≤dis[i]-dis[i-1]≤num_i,即i时刻能雇佣的收营员数量。<=> dis[i]≥dis[i-1]和dis[i-1]≥dis[i]-num_i\\\\dis[i]-dis[i-8]≥r_i,8≤i≤24,即i时刻在岗收营员数量不少于r_i。<=>dis[i]≥dis[i-8]+r_i\\\\dis[i]+dis[24]-dis[i+16]≥r_i,1≤i≤7,同上。<=>dis[i]≥dis[i+16]-dis[24]+r_i\end{cases} 0dis[i]dis[i1]numii<=>dis[i]dis[i1]dis[i1]dis[i]numidis[i]dis[i8]ri8i24iri<=>dis[i]dis[i8]+ridis[i]+dis[24]dis[i+16]ri1i7<=>dis[i]dis[i+16]dis[24]+ri

注意:

对 于 第 3 个 不 等 式 , d i s [ 24 ] 也 是 变 量 , 不 能 转 化 为 差 分 约 束 问 题 。 对于第3个不等式,dis[24]也是变量,不能转化为差分约束问题。 3dis[24]

但 由 于 每 个 时 刻 的 岗 位 不 超 过 1000 , 我 们 可 以 枚 举 d i s [ 24 ] 的 具 体 值 。 但由于每个时刻的岗位不超过1000,我们可以枚举dis[24]的具体值。 1000dis[24]

我 们 最 终 目 标 就 是 求 d i s [ 24 ] , 即 [ 1 , 24 ] 时 间 段 内 最 少 需 要 雇 佣 多 少 收 营 员 才 能 满 足 需 求 。 我们最终目标就是求dis[24],即[1,24]时间段内最少需要雇佣多少收营员才能满足需求。 dis[24][1,24]

所 以 我 们 从 小 到 大 枚 举 d i s [ 24 ] , 第 一 次 成 功 后 就 b r e a k 输 出 。 所以我们从小到大枚举dis[24],第一次成功后就break输出。 dis[24]break

源点的选取:

根 据 第 一 个 不 等 式 , 我 们 要 建 立 边 i − 1 − > i , 选 取 编 号 不 属 于 [ 1 , n ] 的 点 即 可 。 根据第一个不等式,我们要建立边i-1->i,选取编号不属于[1,n]的点即可。 i1>i[1,n]

选 择 0 号 点 为 虚 拟 源 点 , 初 始 化 d i s [ 0 ] = 0 。 选择0号点为虚拟源点,初始化dis[0]=0。 0dis[0]=0

由 于 d i s [ 24 ] 是 我 们 枚 举 的 常 量 , 故 每 次 需 要 建 立 常 数 边 d i s [ 24 ] 。 由于dis[24]是我们枚举的常量,故每次需要建立常数边dis[24]。 dis[24]dis[24]

即 d i s [ 24 ] ≥ 0 + c = d i s [ 0 ] + c , 需 要 建 立 0 − > 24 权 值 为 c 的 边 , 和 24 − > 0 权 值 为 − c 的 边 。 即dis[24]≥0+c=dis[0]+c,需要建立0->24权值为c的边,和24->0权值为-c的边。 dis[24]0+c=dis[0]+c0>24c24>0c

代码:

#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>

using namespace std;

const int N=30, M=100, inf=0x3f3f3f3f;

int T,n,m;
int e[M],ne[M],w[M],h[N],idx;
int dis[N],cnt[N];   //dis[i] : 从1到i这u这段时间最多能有多少收营员
int r[N],num[N];     //r[i] : 时刻i需要多少收营员  num[i] : 时刻i最多有多少岗位可供申请
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;
    
    for(int i=1;i<=24;i++)  
    {
        add(i-1,i,0);    // 0<=dis[i]-dis[i-1]<=num[i]
        add(i,i-1,-num[i]);  //dis[i]>=dis[i-1]  &&  dis[i-1]>=dis[i]-num[i]
    }
    for(int i=8;i<=24;i++) add(i-8,i,r[i]);  // dis[i]-dis[i-8]>=r[i]  i时刻之前8小时内的收营员人数要不少于需求
    for(int i=1;i<=7;i++) add(i+16,i,-c+r[i]);  //dis[i]+dis[24]-dis[i+16]>=r[i]
    
    add(0,24,c),add(24,0,-c);  //dis[24]是常量,dis[24]=c,在图中体现为0->24的边权为c
}

bool spfa(int c)
{
    build(c);
    
    memset(st,false,sizeof st);
    memset(dis,-0x3f,sizeof dis);
    memset(cnt,0,sizeof cnt);
    
    queue<int> Q;
    Q.push(0);
    dis[0]=0;
    st[0]=true;
    
    while(Q.size())
    {
        int u=Q.front();
        Q.pop();
        st[u]=false;
        
        for(int i=h[u];~i;i=ne[i])
        {
            int j=e[i];
            if(dis[j]<dis[u]+w[i])
            {
                dis[j]=dis[u]+w[i];
                cnt[j]=cnt[u]+1;
                if(cnt[j]>=24+1) return true;
                if(!st[j])
                {
                    st[j]=true;
                    Q.push(j);
                }
            }
        }
    }
    
    return false;
}

int main()
{
    scanf("%d",&T);
    while(T--)
    {
        for(int i=1;i<=24;i++) scanf("%d",&r[i]);
        scanf("%d",&n);
        
        memset(num,0,sizeof num);
        for(int i=1;i<=n;i++) 
        {
            int t;
            scanf("%d",&t);
            num[t+1]++;
        }
        
        bool flag=false;
        for(int i=0;i<=1000;i++)
            if(!spfa(i))
            {
                printf("%d\n",i);
                flag=true;
                break;
            }
            
        if(!flag) puts("No Solution");
    }
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值