POJ1275 Cashier Employment (差分约束系统 + 二分答案)

来源:互联网 发布:js数组包含另一个数组 编辑:程序博客网 时间:2024/06/05 17:13

POJ1275 Cashier Employment

原题地址:http://poj.org/problem?id=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,测试数据之间没有空行。

数据范围
数据组数<=20,0 <= N <= 1000,0<=ti<=23,0<=Ri<=1000

题解:
(含未知数的不等式->二分答案 )

题目给出的是每个时刻需要的出纳员数量,容易想到的是根据R建立不等式,但是要求雇佣的人数量,用R并不好求。

转换思路:考虑每个时刻雇佣的出纳员数量,因为工作时间固定,所以可以通过前一段时间的雇用人数之和来较为简单地求出每个时刻在岗人数。

t[i]表示第i时刻开始上班的出纳员数量,s[i]是t[i]的前缀和。
可以知道t[24]-t[0]就是雇佣总人数。

可以得到以下方程 :
s[i]-s[i-1]<=t[i]
s[i]-s[i-1]>=0;
s[i]-s[i-8]>=r[i]
s[24]-s[i+16]+s[i]>=r[i] (从前一天i+17工作到i)

统一不等号方向:
s[i]-s[i-1]<=t[i]
s[i-1]-s[i]<=0;
s[i-8]-s[i]<=-r[i]
s[i+16]-s[i]<=s[24]-r[i]
s[0]-s[24]<=-sum

发现不等式 s[i+16]-s[i]<=s[24]-r[i] 中含未知量,不能直接建边,而sum=s[24]-s[0]就是所求答案,所以二分答案或从小到大枚举s[24]即sum,spfa判环来看可不可行。

代码:

#include<cstdio>#include<iostream>#include<algorithm>#include<cstring>#include<queue>using namespace std;const int M=30;const int N=1005;int T,n,r[M],t[M],head[M],to[N],nxt[N],w[N],num=0,dis[M],cnt[M],inf;bool inq[M];queue<int> Q;void init(){    memset(head,0,sizeof(head));    memset(t,0,sizeof(t));    num=0;}void build(int u,int v,int ww){    num++;    to[num]=v;    nxt[num]=head[u];    w[num]=ww;    head[u]=num;}bool spfa(int x){    memset(dis,0x3f,sizeof(dis));    memset(inq,0,sizeof(inq));    memset(cnt,0,sizeof(cnt));    int st=24;    while(!Q.empty()) Q.pop();    Q.push(st); dis[st]=0;  inq[st]=1;    while(!Q.empty())    {        int u=Q.front(); Q.pop();        inq[u]=0;        for(int i=head[u];i;i=nxt[i])        {            int v=to[i];            if(dis[v]>dis[u]+w[i])            {                dis[v]=dis[u]+w[i];                if(!inq[v])                {                    inq[v]=1;                    Q.push(v);                    cnt[v]++;                    if(cnt[v]>25) return 0;                }            }        }    }    return 1;}bool solve(int x){    memset(head,0,sizeof(head)); num=0;    for(int i=1;i<=24;i++)    {        build(i-1,i,t[i]);        build(i,i-1,0);        if(i>=8) build(i,i-8,-r[i]);        if(i+16<24) build(i,i+16,x-r[i]);    }    build(24,0,-x);    return spfa(x);}int main() {    scanf("%d",&T);    while(T--)    {        init();        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);            t[x+1]++;        }        bool flag=0;        for(int i=0;i<=n;i++)        {            if(solve(i)) {printf("%d\n",i); flag=1;break;}        }        if(!flag) printf("No Solution\n");    }    return 0;}

关于差分约束系统:

常用转化:
1.子段和转前缀和相减。
2.等式转不等式:A+B==C -> A+B≥C 且 A+B≤C
3.给出>或<,由于是整数,a< b转化为a+1<=b,c>b转化为b<=c-1
4.不等式含有未知数,通过二分确定最大/小解

一些注意事项:
不等号统一后,未知量在左,已知量在右。

根据不等号方向,判断跑最长路还是最短路。(还是画三角不等式来判断。一般来说>=跑最长路,<=跑最短路)
这里写图片描述

注意求某个值时,根据是>=还是<=,因为移过项,得出的值的正负也不一样,跑最短路的起点也不一样。

(比如这道题,因为我是移成<=,由此得到的是24向0连边,因此跑最短路以24为起点)

阅读全文
0 0
原创粉丝点击