2015 10 08

来源:互联网 发布:html5网页小游戏源码 编辑:程序博客网 时间:2024/06/06 20:12

问题 E:出纳员的雇佣

时间限制: 1 Sec

内存限制: 128 MB

提交: 16

解决: 12

提交状态

题目描述

德黑兰的一家每天24小时营业的超市,需要一批出纳员来满足它的需要。超市经理雇佣你来帮他解决他的问题——超市在每天的不同时段需要不同数目的出纳员(例如:午夜时只需一小批,而下午则需要很多)来为顾客提供优质服务。他希望雇佣最少数目的出纳员。

经理已经提供你一天的每一小时需要出纳员的最少数量——R0, R1, ..., R23R0表示从午夜到上午100需要出纳员的最少数目,R1表示上午100200之间需要的,等等。每一天,这些数据都是相同的。有N人申请这项工作,每个申请者I在没24小时中,从一个特定的时刻开始连续工作恰好8小时,定义tI0tI23)为上面提到的开始时刻。也就是说,如果第I个申请者被录取,他(她)将从tI时刻开始连续工作8小时。

你将编写一个程序,输入RII = 0..23)和tI I = 1..N),它们都是非负整数,计算为满足上述限制需要雇佣的最少出纳员数目。在每一时刻可以有比对应的RI更多的出纳员在工作。

输入

输入文件的第一行为测试点个数(<= 20)。每组测试数据的第一行为24个整数表示R0R1..., R23RI1000)。接下来一行是N,表示申请者数目(0N1000),接下来每行包含一个整数tI 0tI23)。两组测试数据之间没有空行。

输出

对于每个测试点,输出只有一行,包含一个整数,表示需要出纳员的最少数目。如果无解,你应当输出“No Solution”。

样例输入

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

样例输出

1

题解:一眼看上去,没思路,再看,还是没思路;查查题解,竟然是差分约束,坑。。。。。。。。

共给了23个点的信息,为了好处理,向后移一位,1---24小时就可以了,d[i]表示前i个小时一共雇佣了多少出纳员,hav[i]表示在第i时刻申请雇佣的人数,ned[i]表示第i时刻至少有的人数,根据关系,可得到以下几个式子:

d[i]-d[i-1]>=0;  d[i]-d[i-1]<=hav[i];  d[i]-d[i-8]>=ned[i];(i>=8)d[i-8+24]-d[i]<=lim-ned[i];(i<8)

最后 d[24]-d[0]>=lim;接下来,就是求解24--à0的最短路了;

二分答案,比较dis[0]是否等于枚举的lim,如果满足,再小点,可能更优;不满足,再大点,

顺便判断一下负环(用bellford判断就行了);

 

#include<cstdio>#include<cstring>#include<queue>using namespace std;int head[29],dis[29],Num,ned[29],hav[29],ans,tot,xi;int num[29],l,r,T;bool vis[29];struct dd{int begin,end,juli,next;}jie[25000];void add(int x,int y,int z){    jie[++tot].end=y; jie[tot].juli=z; jie[tot].next=head[x]; head[x]=tot;}void init(){    for(int i=1;i<=24;++i) scanf("%d",&ned[i]);scanf("%d",&Num);for(int i=1;i<=Num;++i){  scanf("%d",&xi); xi++;  hav[xi]++;}}bool spfa(int x){for(int i=0;i<=24;++i) {dis[i]=99999999; vis[i]=0; num[i]=0;}queue<int>que;dis[24]=0; vis[24]=1; num[24]=1;que.push(24);while(!que.empty()){int yu=que.front(); vis[yu]=0; que.pop();if(num[yu]>25) return 0;for(int i=head[yu];i!=-1;i=jie[i].next){int lin=jie[i].end;if(dis[lin]>dis[yu]+jie[i].juli){dis[lin]=dis[yu]+jie[i].juli;if(!vis[lin]){vis[lin]=1;num[lin]++;que.push(lin);}}}}if(dis[0]==-x) return 1;return 0;}bool judge(int mid){tot=0;memset(head,-1,sizeof(head));for(int i=1;i<=24;++i){add(i,i-1,0);add(i-1,i,hav[i]);}for(int i=1;i<8;++i) add(i,i+16,mid-ned[i]);for(int i=8;i<=24;++i) add(i,i-8,-ned[i]);add(24,0,-mid);if(spfa(mid)) return true;return false;}void work(){l=0,r=Num; ans=-1;while(l<=r){int mid=(l+r)>>1;if(judge(mid)){   ans=mid;   r=mid-1;}else l=mid+1;}if(ans==-1) puts("No Solution");else printf("%d\n",ans);}int main(){freopen("ployment.in","r",stdin);freopen("ployment.out","w",stdout);scanf("%d",&T);for(int op=1;op<=T;++op){  memset(hav,0,sizeof(hav));  init(); work();}}

问题 H:借教室(Day 2

时间限制: 1 Sec

内存限制: 128 MB

提交: 37

解决: 15

提交状态

题目描述

在大学期间,经常需要租借教室。大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室。教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样。

面对海量租借教室的信息,我们自然希望编程解决这个问题。

我们需要处理接下来n天的借教室信息,其中第i天学校有ri个教室可供租借。共有m份订单,每份订单用三个正整数描述,分别为dj, sj, tj,表示某租借者需要从第sj天到第tj天租借教室(包括第sj天和第tj天),每天需要租借dj个教室。

我们假定,租借者对教室的大小、地点没有要求。即对于每份订单,我们只需要每天提供dj个教室,而它们具体是哪些教室,每天是否是相同的教室则不用考虑。

借教室的原则是先到先得,也就是说我们要按照订单的先后顺序依次为每份订单分配教室。如果在分配的过程中遇到一份订单无法完全满足,则需要停止教室的分配,通知当前申请人修改订单。这里的无法满足指从第sj天到第tj天中有至少一天剩余的教室数量不足dj个。

现在我们需要知道,是否会有订单无法完全满足。如果有,需要通知哪一个申请人修改订单。

 

输入

每组输入数据的第一行包含两个正整数n, m,表示天数和订单的数量。

第二行包含n个正整数,其中第i个数为ri,表示第i天可用于租借的教室数量。

接下来有m行,每行包含三个正整数dj, sj, tj,表示租借的数量,租借开始、结束分别在第几天。

每行相邻的两个数之间均用一个空格隔开。天数与订单均用从1开始的整数编号。

 

数据规模:

对于10%的数据,有1≤n, m≤10

对于30%的数据,有1≤n, m≤1000

对于70%的数据,有1≤n, m≤105

对于100%的数据,有1≤n, m≤1060≤ri, dj≤1091≤sj≤tj≤n

 

输出

如果所有订单均可满足,则输出只有一行,包含一个整数0。否则(订单无法完全满足)输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。

 

下面是对样例数据的解释:

1份订单满足后,4天剩余的教室数分别为0323。第2份订单要求第2天到第4天每天提供3个教室,而第3天剩余的教室数为2,因此无法满足。分配停止,通知第2个申请人修改订单。

 

样例输入

4 3

2 5 4 3

2 1 3

3 2 4

4 2 4

样例输出

-1

2

 

题解;

好题啊,坑死我了;

线段树无延迟标记本可以过几个点,可某某老师竟然删掉了小测试点,把大测试点复制了两遍,分差拉的老大了。。废话的不说了,上题解;

解法1:

线段树70分,裸的线段树操作,加上延迟标记,95分;

解法2:

二分订单数量,判断一下前mid个订单是否可以。具体操作是前缀和处理,即每读入一个订单就在起始天+要借的房间数量,在结束天的下一天减去要借的房间数量。(#)

然后比较每一天的前缀和与本天总共的房间数的大小,如果房间数<前缀和,就说明前mid个订单有问题,向前二分;否则就向后二分。

(#)证明:在一个订单的起始天+要借的房间数量,在结束天的下一天减去要借的房间数量。设一个数组c[i],记录前缀和。读入的数据是d,s,t   C[s]:=c[s]+d;c[t+1]:=c[t+1]-d;

那么如果第i天在s和t之间,那么前i天的sum{c[i]}中有c[s],相当于已经记下第i天的订单数量了。如果第i天在t之后,前i天的sum{c[i]}中有c[s]和c[t],因为c[s]+d+c[t+1]-d=c[s]+c[t],所以这个订单只对s和t中间天数起作用。得证

正解二分。。。似乎是这样的

用某一天的前缀和表示该天需要的教室数

比如一开始数列a是0 0 0 0 0 0

前缀和0 0 0 0 0 0

3到5天需要2的教室

将a[3]+=2,a[6]-=2

数列变为0 0 2 0 0 -2

前缀和变为0 0 2 2 2 0

这样就实现了增加3-5需要的教室数

然后我们二分订单数

处理前mid个订单看看是否有某一天的前缀和大于di

#include<cstdio>#include<cstring>using namespace std;int b[1000005],a[1000005],A[1000006],B[1000006],C[1000006],n,m,l,r,ans,sum;inline int in(){char c=getchar();int x=0;while(c<'0'||c>'9') c=getchar();for(;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';return x;}bool judge(int x){sum=0;memset(b,0,sizeof(b));for(int i=1;i<=x;++i) b[B[i]]+=A[i],b[C[i]+1]-=A[i];for(int i=1;i<=n;++i) {sum+=b[i]; if(sum>a[i]) return 0;}return 1;}int main(){    freopen("classroom.in","r",stdin);freopen("classroom.out","w",stdout);n=in(); m=in();for(int i=1;i<=n;++i) a[i]=in();for(int i=1;i<=m;++i){A[i]=in(); B[i]=in(); C[i]=in();}l=1;r=m;while(l<=r){  int mid=(l+r)>>1;  if(!judge(mid)){ans=mid; r=mid-1;  }  else l=mid+1;}if(!ans) puts("0");else {printf("-1\n%d",ans);}}


0 0
原创粉丝点击