Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 7905 | Accepted: 3017 |
Description
The manager has provided you with the least number of cashiers needed for every one-hour slot of the day. This data is given as R(0), R(1), ..., R(23): R(0) represents the least number of cashiers needed from midnight to 1:00 A.M., R(1) shows this number for duration of 1:00 A.M. to 2:00 A.M., and so on. Note that these numbers are the same every day. There are N qualified applicants for this job. Each applicant i works non-stop once each 24 hours in a shift of exactly 8 hours starting from a specified hour, say ti (0 <= ti <= 23), exactly from the start of the hour mentioned. That is, if the ith applicant is hired, he/she will work starting from ti o'clock sharp for 8 hours. Cashiers do not replace one another and work exactly as scheduled, and there are enough cash registers and counters for those who are hired.
You are to write a program to read the R(i) 's for i=0..23 and ti 's for i=1..N that are all, non-negative integer numbers and compute the least number of cashiers needed to be employed to meet the mentioned constraints. Note that there can be more cashiers than the least number needed for a specific slot.
Input
Output
If there is no solution for the test case, you should write No Solution for that case.
Sample Input
1 1 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 5 0 23 22 1 10
Sample Output
1
嗯,这道题搞了两天,看着博客,看着书,画了也是不少纸好不容易是弄得差不多吧,也不是完全理解吧。经过这道题让我真正认识到了,差分约束系统并不是很简单的东西,难点也是在建模上,就是你要根据这些条件能列出哪些限制条件来,让后根据这些限制条件求解。
其实什么题,重点就是知道利用那种知识,然后建出相应的模来。
题意:
题意:(直接搬得)
德黑兰的一家每天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,测试数据之间没有空行。
输出描述:
对输入文件中的每个测试数据,输出占一行,为需要雇佣的出纳员的最少数目。如果某个测试数据没有解。则输出"No Solution"。
分析:由于题目给定的是某一时刻的信息,但是试图描述的却是某一时间段的信息,所以处理的时候将时间点转化为时间段。对于每一个时间段x(每一小时作为一个时间段处理)定义出一个变量 s[x] 表示从1到x时间段一共雇佣了多少员工。则对于题目中所描述的数据能够较好的构图,关键还能够解决雇佣多少人的问题,而如果采用 s[x] 表示x时段工作的员工的人数,虽然能够列出方程但不利于问题的求解。
约定 s[x] 表示x时间段至少要有多少员工在,t[x] 表示x时段有多少人申请工作,ans表示欲招的员工个数(也就是题目中要求的值)。对于题目中给定的信息构图如下:
1.s[i]-s[i-1]>=0 雇佣的人数肯定是大于0的
2.s[i]-s[i-1]<=t[i] 当前点最多能雇佣的人自然是不能超过t[i]的
3.s[i]-s[i-8]>=r[i] s[i]-s[i-8] 因为i点之前8小时雇佣的是下班的,所以这个值表示的新雇佣来的人可以工作到i点雇员们,所以是比r[i]大或者相等的(8<=i<=24)
4.s[i]+ans-s[i+16]>=r[i] 这个其实就是上一个式子的变形只不过因为(0<=i<=7)所以隔天的值需要加上上一天的人数也就是ans。而比i小8的值是多少呢,其实就是24-(8-i)变形之后就是i+16了。而s[i]也应该是s[i]+ans
5.s[24]-s[0]>=ans 雇佣的人数肯定要满足这个的,因为即使你前面限制条件符合,这个不符合也是不行的。我觉得这个条件换成等号就可以了
讲真,找全条件真的是很难得,以后就得靠经验来做这样的题吧。
我们要求的就是ans要使的s[24]-s[0]的值最小就是使等号成立,在图中的表示形式是s[0]-s[24]<=-ans所以求一个24到0的最短路,看看最后结果能不能得出-ans来。
对于ans,它的结果是1-m,所以可以二分,当你不符合条件的时候,说明所求出的最短路比-ans还要小,所以我们要增加边的权值,ans值便应该变大。这样便会更容易接近-ans的值。
直接枚举:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int n=24;
const int MAXN=24+10;
const int inf =1e9;
int t[MAXN],r[MAXN];
int dis[MAXN],sum[MAXN];
bool vis[MAXN];
int cnt;
int head[MAXN];
struct node
{
int v;
int w;
int next;
}e[MAXN<<2];
void add(int u,int v,int w)
{
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt++;
}
void build(int ans)
{
int i;
for(i=1;i<=24;++i)
{
add(i,i-1,0);
add(i-1,i,t[i]);
}
for(i=0;i<8;++i)
{
add(i,i+16,ans-r[i]);
}
for(i=8;i<=24;++i)
{
add(i,i-8,-r[i]);
}
add(24,0,-ans);
}
bool spfa(int ans)
{
int i;
for(i=0;i<=24;++i)
{
dis[i]=inf;
sum[i]=0;
vis[i]=0;
}
dis[24]=0;
int st=0,ed=0;
int q[MAXN];
q[ed++]=24;
sum[24]++;
while(st<ed)
{
int u=q[st%MAXN];
st++;
vis[u]=0;
for(i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
int w=e[i].w;
int t=dis[u]+w;
if(dis[v]>t)
{
dis[v]=t;
if(!vis[v])
{
vis[v]=1;
sum[v]++;
if(sum[v]>24)return 0;
q[ed%MAXN]=v;
ed++;
}
}
}
}
return dis[0]-dis[24]==-ans;
}
void init()
{
cnt=0;
for(int i=0;i<=24;++i)
{
head[i]=-1;
}
}
int main()
{
int n;
int i;
int m;
int x;
scanf("%d",&n);
while(n--)
{
for(i=1;i<=24;++i)
{
scanf("%d",&r[i]);
t[i]=0;
}
scanf("%d",&m);
for(i=1;i<=m;++i)
{
scanf("%d",&x);
t[x+1]++;
}
for(i=1;i<=m;++i)
{
init();
build(i);
if(spfa(i))
{
printf("%d\n",i);
break;
}
}
if(i>m)puts("No Solution");
}
return 0;
}
二分:(当遇到符合的值得时候记录下来,然后缩小右边界,继续寻找)这才是二分的正确使用方法
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int n=24;
const int MAXN=24+10;
const int inf =1e9;
int t[MAXN],r[MAXN];
int dis[MAXN],sum[MAXN];
bool vis[MAXN];
int cnt;
int head[MAXN];
struct node
{
int v;
int w;
int next;
}e[MAXN<<2];
void add(int u,int v,int w)
{
e[cnt].v=v;
e[cnt].w=w;
e[cnt].next=head[u];
head[u]=cnt++;
}
void build(int ans)
{
int i;
for(i=1;i<=24;++i)
{
add(i,i-1,0);
add(i-1,i,t[i]);
}
for(i=0;i<8;++i)
{
add(i,i+16,ans-r[i]);
}
for(i=8;i<=24;++i)
{
add(i,i-8,-r[i]);
}
add(24,0,-ans);
}
bool spfa(int ans)
{
int i;
for(i=0;i<=24;++i)
{
dis[i]=inf;
sum[i]=0;
vis[i]=0;
}
dis[24]=0;
int st=0,ed=0;
int q[MAXN];
q[ed++]=24;
sum[24]++;
while(st<ed)
{
int u=q[st%MAXN];
st++;
vis[u]=0;
for(i=head[u];i!=-1;i=e[i].next)
{
int v=e[i].v;
int w=e[i].w;
int t=dis[u]+w;
if(dis[v]>t)
{
dis[v]=t;
if(!vis[v])
{
vis[v]=1;
sum[v]++;
if(sum[v]>24)return 0;
q[ed%MAXN]=v;
ed++;
}
}
}
}
return dis[0]-dis[24]==-ans;
}
void init()
{
cnt=0;
for(int i=0;i<=24;++i)
{
head[i]=-1;
}
}
void get_ans(int l,int r)
{
int pos=-1,mid;
while(l<=r)
{
mid=(l+r)>>1;
init();
build(mid);
if(spfa(mid))
{
pos=mid;
r=mid-1;
}
else l=mid+1;
}
if(pos!=-1)printf("%d\n",pos);
else puts("No Solution");
}
int main()
{
int n;
int i;
int m;
int x;
scanf("%d",&n);
while(n--)
{
for(i=1;i<=24;++i)
{
scanf("%d",&r[i]);
t[i]=0;
}
scanf("%d",&m);
for(i=1;i<=m;++i)
{
scanf("%d",&x);
t[x+1]++;
}
/*for(i=1;i<=m;++i)
{
init();
build(i);
if(spfa(i))
{
printf("%d\n",i);
break;
}
}
if(i>m)puts("No Solution");
*/
get_ans(1,m);
}
return 0;
}