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

POJ 1275

题目大意

一个商店每天24个时段各有一个需要的职工数 Ri(0Ri23) ,有n个职工 (0n1000) 可以用,每个职工从一个各自的开始时间 ti 连续工作8小时,问最少需要多少职工能满足需要,不能满足输出无解。

分析

尝试以不同的方式去描述一个问题是着手分析的一个很好的方式,将一个问题数学化也是一种常用的方法。

  • 错误思路

    注意到职工数n最多有1000个,但是开始时间 ti 的情况最多只有23中,也就是说大多数职工其实是等价的,可以合并起来处理。重述一下问题:

    xi 表示在时间i开始工作的员工数,由于这个时间开始工作的员工数是有限的,所以需要满足约束条件 (0xiCi) 。令 yi 表示在时段i工作的职工数, yi 可以由 x 得出。变成形如下面的约束方程

    xa1+xa2+...+xakR0xb1+xb2+...+xbkR1...min(x0+x1+...x23)

    这种形式的整数规划是一个NP-hard问题,这个问题可以归约到0-1整数规划,但也只是这种形式的整数规划的特殊形式,这种形式的整数规划并不能还原到我们的这个问题上去,也就是说我们要求解的问题和这种形式的整数规划并不是等价的。因此不能从这种形式的整数规划的普遍解法的角度考虑这个问题,可以通过观察这个约束方程的特征希望能够找到优化的方法是一个思路。

    我们应该注意到一个规律就是:如果将 x 按照职工开始工作的时间进行编号的话,那么约束方程的每一个约束不等式的编号将是连续的,并且每个不等式的变量个数是相同的。

    也就是说不等式是形如:

    x17+x18+...+x23+x0R0x18+...+x23+x0+x1R1...x16+x17+...+x22+x23R23min(x1+x2+...x23)

    根据这条启发式信息,既然每个不等式的变量数相同都为8且具有相同的7项,那么可以通过两式相减就变成形如 abc 的经典差分约束不等式了:

    x17x1R0R1x18x2R1R2...x15x23R22R23x16x0R23R0

    要求 min(x0+x1+...+x23)

    我们可以新建一个源点 x24=0

    再加上每个变量满足 (0xici) 条件:

    {x24xiCixix240

    但是这样是没法求出 min(x0+x1+...+x23)

    然后就卡在这里了!!!

  • 再分析

    想不出思路后,百度,在这里继续写一下分析的过程:

    既然将 x0,x1...x23 分开求是得不到它们和的最小值的,那么我们可以用一个变量 si 表示时刻i及之前实际开始工作的总职工数。那么有 (s1=0)

    0sisi1Cisis(i8)Risi+s23si+16Ri0i238i230i7

    转化成标准形式后就是(下标做了+1处理):
    sisi10si1siCisis(i8)Risis(i+16)Ris24s24s0s24min(s24)1i241i248i231i7s24ans

    由于上面第四个不等式由三个变量,但 s24 比较特殊,所以可以用二分的方法对每一个 s24 判断是否可行。

总结

其实这道题也不是特别难,下标处理上稍微复杂了一点,一开始思路走错了没有想到先区间求和的处理。

又是一道弄了一天的题。。。不过就是要多做点这样的题才能使自己的能力提升。
看见博客有用单纯形法求解线性规划的,虽然效率低一点了,但很通用啊,有空学习一下:POJ1275 Cashier Employment[差分约束系统 || 单纯形法]

代码

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<algorithm>
#include<set>
using namespace std;
const int INF=0x7fffffff;
int T;
int R[25];
int n;
int C[25];//C[i]表示以i作为开始时间的时段的职工数目
int s[25];
struct Edge
{
      int to;
      int next;
      int w;
}edge[201005];
int edgecount;
int flag;//flag=0表示无环
int head[1005];
int dis[1005];
int vis[1005];
int in[1005];//表示某个点进队的次数
void Init()
{
      memset(head,-1,sizeof(head));
      memset(vis,0,sizeof(vis));
      memset(in,0,sizeof(in));
      edgecount=0;
      flag=0;
}
void Add_edge(int u,int v,int w)
{
      edge[++edgecount].to=v;
      edge[edgecount].w=w;
      edge[edgecount].next=head[u];
      head[u]=edgecount;
}
void Spfa(int s,int ans)//无解flag=1
{
      for(int i=0;i<=24;i++)dis[i]=-INF;
      memset(vis,0,sizeof(vis));
      memset(in,0,sizeof(in));
      dis[s]=0;
      vis[s]=1;//vis为1表示在队列里面
      queue<int>Q;
      Q.push(s);
      while(!Q.empty())
      {
           int u=Q.front();
           Q.pop();
           for(int k=head[u];k!=-1;k=edge[k].next)
           {
                 int t=edge[k].w+dis[u];
                 if(dis[edge[k].to]<t)
                 {
                       dis[edge[k].to]=t;
                       if(vis[edge[k].to]!=1)
                       {
                           Q.push(edge[k].to);
                           vis[edge[k].to]=1;
                           in[edge[k].to]++;
                       }
                       if(in[edge[k].to]>24){flag=1;return ;}
                 }
           }
           vis[u]=0;
      }
      if(dis[24]!=ans)flag=1;
}
void Build(int ans)
{
    int sum=0;
    for(int i=1;i<=24;i++)
    {
        Add_edge(i,i-1,-C[i]);
        Add_edge(i-1,i,0);
    }
    for(int i=9;i<=24;i++)
          Add_edge(i-8,i,R[i]);
    for(int i=1;i<=8;i++)
          Add_edge(i+16,i,R[i]-ans);
    Add_edge(0,24,ans);
}
void Solve()//二分
{
    int L=0;
    int R=n;
    int M;
    int ans=-1;
    while(L<R)
    {
          M=(L+R)/2;
          Init();
          Build(M);
          Spfa(0,M);
          if(flag==0)
          {
              ans=M;
              R=M;
          }
          else L=M+1;
    }
    if(ans!=-1){cout<<ans<<endl;}
    else cout<<"No Solution"<<endl;
}
int main()
{
    scanf("%d",&T);
    int t;
    int ans;
    while(T--)
    {
          ans=0;
          memset(C,0,sizeof(C));
          for(int i=1;i<=24;i++)scanf("%d",&R[i]);
          scanf("%d",&n);
          for(int i=1;i<=n;i++)
          {
              scanf("%d",&t);
              C[t+1]++;
          }
          Solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值