出纳员问题
题目描述
有一家24小时超市招收出纳员,每个时刻需要r[i]个人,有n个竞聘者,他们会从t[i]开始,连续工作8小时,求招收的最少出纳员。
思路
首先我们考虑每个竞聘者的具体时间没有意义,我们可以直接记着n个人中在i时刻开始工作的人数为num[i],为了方便处理,我们把时间改为1~24。那我们再考虑限制条件,假设x[i]表示i时刻实际工作人数,那么我们记x[i]的前缀和为s[i],由此可以得到几个等式:
①s[i]-s[i-1]≤num[i],s[i-1]≤s[i]
②当9≤i≤24时,s[i]-s[i-8]≥r[i]
③当1≤i≤8时,s[24]+s[i]-s[i+16]≥r[i]
由此我们就可以通过移项得到差分约束所需要的式子,不过第三个式子有一些特殊,它包含一个s[24],不过分析可知s[24]就是实际雇佣的总人数,所以我们可以二分总人数,对于每个总人数建出图后,用dfs判断图上是否存在负环即可。
代码
#include <bits/stdc++.h> using namespace std; const int N=1100,M=1e4+10; int nxt[M],to[M],w[M],head[N],tot; int dis[N],num[30],r[30],vis[N]; bool f; void add_edge(int x,int y,int v) { nxt[++tot]=head[x]; head[x]=tot; to[tot]=y; w[tot]=v; } void clear() { memset(head,0,sizeof(head)); memset(vis,0,sizeof(vis)); memset(dis,0,sizeof(dis)); tot=0;f=0; } void spfa(int u,int st) { if(f)return ; vis[u]=st; // cout<<u<<endl; 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(!vis[v])spfa(v,st); if(vis[v]==st) { f=1;return ; } } } vis[u]=0; } bool check(int k) { clear(); for(int i=1;i<=24;i++) { add_edge(i-1,i,0); add_edge(i,i-1,-num[i]); } 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]-k); add_edge(0,24,k); for(int i=1;i<=24;i++) { spfa(i,i); if(f)return 0; } return 1; } int main() { int t; scanf("%d",&t); while(t--) { memset(num,0,sizeof(num)); int n; 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); num[x+1]++; } int l=0,r=n,ans=-1; while(l<=r) { int mid=(l+r)>>1; if(check(mid))ans=mid,r=mid-1; else l=mid+1; } // cout<<check(1)<<endl; if(ans!=-1)printf("%d\n",ans); else printf("No Solution\n"); } return 0; }