poj 1275 & hdu 1529 Cashier Employment

题目链接

题目大意

一个店在不同的时间需要不同数量的店员.
现在又n个店员来应聘,如果聘用一个店员,那么他会工作8个小时.
每个应聘者都有一个工作开始时间.
问最少雇佣几个应聘者.

题解

这是一道论文题.
2006 冯威的<数与图的完美结合——-浅析差分约束系统>
首先定义一堆的数组.
定义r[i]表示在i时店里需要r[i]个店员.
x[i]表示在i时店长可以雇佣x[i]个店员
t[i]表示在i时店长实际雇佣了t[i]个店员
差分约束系统的关键就是找到大小关系,那么可以挖掘题目信息来推一推.
我在推导时习惯化成
由于一个时间点可以雇佣的店员一定,那么

t[i]x[i]

由于一个时间点在店中的店员必须大于r[i]
那么对于 i8
j=i7it[j]r[i]
对于 i<8 ,则有
j=0jit[j]+j=i+1623t[j]r[i]
同时,一个时间点的雇佣人数不可以为负,所以 t[i]0 显然,这些都可以用前缀和维护. 令s[i]表示t[i]的前缀和. 于是这一堆得 Σ 就变成了:
s[i1]s[i]x[i]s[i]s[i8]r[i]                   i8s[i]s[i+16]r[i]s[23]    i<8s[i]s[i1]0

但是 s[23] 并不是一个常量,这就给我们带来了不少麻烦.
由于数据范围比较小,可以考虑枚举 s[23] ,然后再判断是不是可行.
于是每次重构第三条式子所代表的边.由于每次要清零,所以正向表比较方便.
构完图之后跑一边最长路,如果出现负圈等等一些条件就是不满足.
s[23]的取值明显满足二分性质,可以改成二分查找.

#include <cstdio>
#include <algorithm>
using namespace std;
const int M=1005;
const int INF=2000000000;
int head[M],etot,r[M],s[M],x[M],Q[M*40];
struct Edge{
    int to,v,nxt;
    Edge(int _to=0,int _v=0,int _nxt=0):to(_to),v(_v),nxt(_nxt){}
}edge[M<<2];
void add_edge(int a,int b,int c){
    edge[etot]=Edge(b,c,head[a]);
    head[a]=etot++;
}
int cnt[M],tmphead[M],n;
bool mark[M];
bool SPFA(){
    for(int i=0;i<=24;i++)
        s[i]=-INF,cnt[i]=mark[i]=0;
    s[24]=0;
    int L=0,R=0;
    Q[R++]=24;
    while(L<R){
        int x=Q[L++];
        mark[x]=0;
        for(int i=head[x];~i;i=edge[i].nxt){
            int to=edge[i].to;
            if(s[to]<s[x]+edge[i].v){
                s[to]=s[x]+edge[i].v;
                cnt[to]++;
                if(cnt[to]==n+1) return 0;//负圈 
                if(!mark[to]){
                    mark[to]=1;
                    Q[R++]=to;
                }
            }
        }
    }
    return 1;
}
void solve(){
    for(int i=0;i<24;i++)
        scanf("%d",&r[i]);
    scanf("%d",&n);
    for(int i=0;i<25;i++)
        head[i]=-1;
    for(int i=0;i<n;i++){
        int a;
        scanf("%d",&a);
        x[a]++;
    }
    etot=0;
    for(int i=8;i<24;i++)//s(i)-s(i-8)>=r(i) 
        add_edge(i,i-8,r[i]);
    //24是起点 
    add_edge(24,0,-x[0]);
    add_edge(0,24,0);
    for(int i=1;i<24;i++){
        add_edge(i-1,i,-x[i]);//s(i-1)-s(i)>=x(i)
        add_edge(i,i-1,0);//x(i)-x(i-1)>=0
    }
    int tmp=etot,ans=-1;
    for(int i=0;i<=24;i++)
        tmphead[i]=head[i];
    int L=0,R=n;
    while(L<=R){//枚举s[23]
        int i=(L+R)>>1;
        for(int j=0;j<8;j++)
            add_edge(j,j+16,r[j]-i);
        add_edge(23,24,i);//这很重要,s[23]>=i 
        if(SPFA()){
            bool f=1;
            for(int j=1;j<24;j++){
                if(s[j-1]-s[j]>x[j]){
                    f=0;
                    break;
                }
            }
            if(f) R=i-1,ans=i;
            else L=i+1;
        }else L=i+1;
        //还原图 
        etot=tmp;
        for(int j=0;j<=24;j++)
            head[j]=tmphead[j];
    }
    if(~ans) printf("%d\n",ans);
    else puts("No Solution");
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--) solve();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值