poj 1275 差分约束(最少需要雇佣多少员工呢)

题意:一家每天24小时营业的超市,需要一批出纳员来满足它的需求。超市在每天的不同时段需要不同数目的出纳员(例如,午夜只需一小批,而下午则需要很多)来为顾客提供优质服务,他希望雇佣最少数目的纳员。 现在给定一天里每一小时需要出纳员的最少数量R(0),R(1),...,R(23)。R(0)表示从午夜到凌晨1:00所需要出纳员的最少数目;R(1)表示凌晨1:00到2:00之间需要的;等等。每一天,这些数据都是相同的。有N人申请这项工作,每个申请者i有一个数值ti(0<=ti<=23),为上面提到的开始时刻。也就是说,如果第i个申请者被录用,他(或她)将从ti时刻开始连续工作8小时。计算为满足上述限制需要雇佣的最少出纳员数目、在每一时刻可以有比对应R(i)更多的出纳员在工作。

思路:用差分约束来建模。设num[ i ]为i时刻能够开始工作的人数,x[ i ]为 第 i 时刻实际雇佣的人数,那么x[ I ]<=num[ I ]。
设r[ i ](输入的每一时刻所需的人数)为i时刻至少需要工作的人数,于是有如下关系:
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 ]
设s[ I ]=x[ 1 ]+x[ 2 ]…+x[ I ],得到
0<=s[ I ]-s[ I-1 ]<=num[ I ], 0<=I<=23
s[ I ]-s[ I-8 ]>=r[ I ], 8<=I<=23 建立关系
s[ 23 ]+s[ I ]-s[ I+16 ]>=r[ I ], 0<=I<=7 建立可以循环的关系

对于以上的几组不等式,我们采用一种非常笨拙的办法处理这一系列的不等式(其实也是让零乱的式子变得更加整齐,易于处理)。首先我们要明白差分约束系统的应用对象(它通常针对多个二项相减的不等式的)于是我们将上面的所有式子都转化成两项未知项在左边,另外的常数项在右边,且中间用>=连接的式子,即:
s[ I ]-s[ I-1 ]>=0 (0<=I<=23)
s[ I-1 ]-s[ I ]>=-num[ I ] (0<=I<=23)
s[ I ]-s[ I-8 ]>=r[ I ] (8<=I<=23)
s[ I ]-s[ I+16 ]>=r[ I ]-sum (0<=I<= 7)

S[23] - S[-1] >= sum;sum为雇佣的出纳员总数。

要加入最后一条边的原因是: 上述式子虽然有解,但求出的s[23]小于代入里的sum!

这时,显然得到的s[]不满足原来的方程了。不过虽然得到的解不满足原方程组,但这并不代表(1)(2)(3)(4)在s[24]=sum时没有可行解!
此外,值得注意的是,当得到的s[23]>sum时,虽然s[23]不一定是最优解,但把ans置成s[23]后,确实是可行解。

所以为了等价原命题,必须再加上条件:s[23]>=sum,这就是所谓加出来的那条边。

代码里用s[24]表示s[-1]

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std;
#define INF 0x3fffffff
#define clr(s,t) memset(s,t,sizeof(s));
#define N 26
int t[N],s[N],first[N],top,T,num,dis[N],cnt[N],used[N];
struct edge{
    int y,next,w;
}e[1000];
void add(int x,int y,int w){
    e[top].y = y;
    e[top].w = w;
    e[top].next = first[x];
    first[x] = top++;
}
void create(int mid){
    int i;
    top = 0;
    clr(first, -1);
    for(i = 1;i<24;i++){
        add(i-1,i,0);
        add(i,i-1,-s[i]);
    }
    add(24,0,0);
    add(0,24,-s[0]);
    for(i = 8;i<24;i++)
        add(i-8,i,t[i]);
    for(i = 0;i<8;i++)
        add(i+16,i,t[i]-mid);
    add(24,23,mid);
}
int relax(int x,int y,int w){
    if(dis[y]<dis[x]+w){
        dis[y] = dis[x]+w;
        return 1;
    }
    return 0;
}
int spfa(int s,int t,int value){
    int i,now;
    queue<int> q;
    for(i = 0;i<=24;i++)
        dis[i] = -INF;
    dis[s] = 0;
    q.push(s);
    clr(used, 0);
    used[s] = 1;
    clr(cnt, 0);
    while(!q.empty()){
        now = q.front();
        used[now] = 0;
        q.pop();
        for(i = first[now];i!=-1;i=e[i].next) {
            if(relax(now,e[i].y,e[i].w) && !used[e[i].y]){
                q.push(e[i].y);
                used[e[i].y] = 1;
                cnt[e[i].y]++;
                if(cnt[e[i].y]==25)
                    return 0;
            }
        }
    }
    return dis[t]==value;
}
int main(){
    scanf("%d",&T);
    while(T--){
        int i,j,low,high,mid;
        clr(s, 0);
        for(i = 0;i<24;i++)
            scanf("%d",&t[i]);
        scanf("%d",&num);
        high = num;
        for(j = 1;j<=num;j++){
            scanf("%d",&i);
            s[i]++;
        }
        low = 1;
        while(low <= high){
            mid = (low+high)>>1;
            create(mid);
            j = spfa(24,23,mid);
            if(j)
                high = mid-1;
            else
                low = mid+1;
        }
        if(low == num+1)
            printf("No Solution\n");
        else
            printf("%d\n",low);
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值