2015 10 08

问题 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);
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值