POJ 1275 Cashier Employment (差分约束 二分)

Cashier Employment POJ - 1275

题意:德黑兰的一家每天24小时营业的超市,需要一批出纳员来满足它的需求。超市经理雇佣你来帮他解决一个问题————超市在每天的不同时段需要不同数目的出纳员(例如,午夜只需一小批,而下午则需要很多)来为顾客提供优质服务,他希望雇佣最少数目的纳员。

超市经历已经提供一天里每一小时需要出纳员的最少数量————R(0),R(1),…,R(23)。R(0)表示从午夜到凌晨1:00所需要出纳员的最少数目;R(1)表示凌晨1:00到2:00之间需要的;等等。每一天,这些数据都是相同的。有N人申请这项工作,每个申请者i在每天24小时当中,从一个特定的时刻开始连续工作恰好8小时。定义ti(0<=ti<=23)为上面提到的开始时刻,也就是说,如果第i个申请者被录用,他(或她)将从ti时刻开始连续工作8小时。

试着编写一个程序,输入R(i),i=0,…,23,以及ti,i=1,…,N,它们都是非负整数,计算为满足上述限制需要雇佣的最少出纳员数目、在每一时刻可以有比对应R(i)更多的出纳员在工作
输入描述:
输入文件的第1行为一个整数T,表示输入文件中测试数据的数目(至多20个)。每个测试数据第一行为24个整数,表示R(0),R(1),…,R(23),R(i)最大可以取到1000。接下来一行是一个整数N,表示申请者的数目,0<=N<=1000。接下来有N行,每行为一个整数ti,0<=ti<=23,测试数据之间没有空行。

输出描述:
对输入文件中的每个测试数据,输出占一行,为需要雇佣的出纳员的最少数目。如果某个测试数据没有解。则输出”No Solution”。

思路:
分享一篇清晰的题解

不等式含有未知数,通过二分确定最大

由于题目给定的是某一时刻的信息,但是试图描述的却是某一时间段的信息,所以处理的时候将时间点转化为时间段。对于每一个时间段x(每一小时作为一个时间段处理)定义出一个变量d[x]表示从1到x时间段一共雇佣了多少员工。则对于题目中所描述的数据能够较好的构图,关键还能够解决雇佣多少人的问题,而如果采用d[x]表示x时段工作的员工的人数,虽然能够列出方程但不利于问题的求解。

约定ned[x]表示x时间段至少要有多少员工在,hav[x]表示x时段有多少人申请工作,lim表示欲招的员工个数。对于题目中给定的信息构图如下:
0 <= d[x] - d[x-1] <= hav[x]; // 雇佣的人数少于申请者但不能为负数
d[x] - d[x-8] >= ned[x] // 当x>=8时,该方程成立,否则将出现负数显然不成立
d[x-8+24] - d[i] <= lim - ned[x] // 当x<8时,由于昨天的雇人可以通宵上班,因此这个约束通过反面处理
d[24] - d[0] >= lim // 最后24小时内雇佣人应该大于等于lim个人

接下来就是求解什么了,我们要得到一天最少的雇佣人数即d[24] - d[0] >= M中的M,因为M是一个变量,所以要求解M的最大值。将式子化为d[0] - d[24] <= -M,M去最大,-M取最小,即求24到0的最短路,再比较d[0]是否等于-lim。最后就是要枚举lim,因为该模型只能够判定一个lim是否能够合法,而不能够一次求出最优解,由于最多有1000人,可以采用二分查找来枚举。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <queue>
#define N 30
#define P 24
using namespace std;

//每个节点的dis[x]表示的含义为从第1个小时到第x个小时已经雇佣的员工数量
int n, ned[N], tot[N];
int idc, head[N];
int dis[N], cnt[N];
bool vis[N];
int ans;

struct Edge {
    int to, w, nxt;        
}ed[2010];

void adde(int u, int v, int w) {
    ed[++idc].to = v;
    ed[idc].w = w;
    ed[idc].nxt = head[u];
    head[u] = idc;
}

void build(int lim) {
    idc = 1;
    memset(head, 0, sizeof (head));
    for(int i=1; i<=P; ++i){
        adde(i-1, i, tot[i]);//雇佣的人数少于申请者 d[i] - d[i-1] <= tot[i]
        adde(i, i-1, 0);//每个小时内雇佣的人数应该是个非负数 d[i] - d[i-1] >= 0
    }
    for(int i=1; i<8; ++i) adde(i, i-8+P, lim-ned[i]);//d[i-8+P] - d[i] <= lim-ned[i]
        //由于无法直接得到从前一天的后几小时到当天前几小时的雇佣人数,所以反面求解
    for(int i=8; i<=P; ++i) adde(i, i-8, -ned[i]); 
        // d[i] - d[i-8] >= ned[i] 前8小时内雇佣人数应该大于等于需求量 
    adde(P, 0, -lim);
    // d[P] - d[0] >= lim 表示至少为lim个员工,这样就能够看在最好情况下能不能取到这个最小值
}

bool spfa(int lim){
    memset(dis, 0x3f, sizeof (dis));
    memset(vis, 0, sizeof (vis));
    memset(cnt, 0, sizeof (cnt));
    queue <int> q;
    dis[P] = 0, vis[P] = 1, cnt[P] = 1;
    q.push ( P );
    while ( !q.empty() ) {
        int u = q.front(); q.pop();
        if (cnt[u] > 25) return 0;//每个点最多被松弛24次 否则就有负环 无解 
        vis[u] = 0;
        for (int i = head[u]; i; i = ed[i].nxt) {
            int v = ed[i].to;  
            if(dis[v] > dis[u] + ed[i].w) {
                dis[v] = dis[u] + ed[i].w;
                if( !vis[v] ) {
                    vis[v] = 1;
                    cnt[v]++;
                    q.push( v );
                }
            }
        }
    }
    return dis[0] == -lim;//0 与 24 的差值为 lim  
}

int main() {
    int T; scanf("%d", &T);
    while ( T-- ) {
        memset(tot, 0, sizeof (tot));
        for(int i=1; i<=P; ++i) scanf("%d", &ned[i]);//0时刻申请将工作在第一个小时
        scanf("%d", &n);
        for(int i=0; i<n; ++i) {
            int t; scanf("%d", &t); tot[t+1]++;//+1 1~24为一天 
        }
        ans = -1;
        int l = 0, r = n;
        while (l <= r) {
            int mid = (l + r) >> 1;//mid 表示一共能够雇佣的员工数量
            build( mid );
            if( spfa( mid ) ) {
                ans = mid;
                r = mid - 1;
            }
            else l = mid + 1;
        }
        if( ans != -1 ) printf("%d\n", ans);
        else puts("No Solution");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值