您的位置:首页 > 其它

2015 10 08

2015-10-10 07:13 302 查看
问题 E:
出纳员的雇佣

时间限制: 1 Sec
内存限制: 128 MB
提交: 16
解决: 12
提交状态
题目描述
德黑兰的一家每天24小时营业的超市,需要一批出纳员来满足它的需要。超市经理雇佣你来帮他解决他的问题——超市在每天的不同时段需要不同数目的出纳员(例如:午夜时只需一小批,而下午则需要很多)来为顾客提供优质服务。他希望雇佣最少数目的出纳员。
经理已经提供你一天的每一小时需要出纳员的最少数量——R0, R1, ..., R23。R0表示从午夜到上午1:00需要出纳员的最少数目,R1表示上午1:00到2:00之间需要的,等等。每一天,这些数据都是相同的。有N人申请这项工作,每个申请者I在没24小时中,从一个特定的时刻开始连续工作恰好8小时,定义tI(0≤tI≤23)为上面提到的开始时刻。也就是说,如果第I个申请者被录取,他(她)将从tI时刻开始连续工作8小时。
你将编写一个程序,输入RI(I = 0..23)和tI (I = 1..N),它们都是非负整数,计算为满足上述限制需要雇佣的最少出纳员数目。在每一时刻可以有比对应的RI更多的出纳员在工作。
输入
输入文件的第一行为测试点个数(<= 20)。每组测试数据的第一行为24个整数表示R0,R1,..., R23(RI≤1000)。接下来一行是N,表示申请者数目(0≤N≤1000),接下来每行包含一个整数tI (0≤tI≤23)。两组测试数据之间没有空行。
输出
对于每个测试点,输出只有一行,包含一个整数,表示需要出纳员的最少数目。如果无解,你应当输出“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≤106,0≤ri,
dj≤109,1≤sj≤tj≤n。
 
输出
如果所有订单均可满足,则输出只有一行,包含一个整数0。否则(订单无法完全满足)输出两行,第一行输出一个负整数-1,第二行输出需要修改订单的申请人编号。
 
下面是对样例数据的解释:
第1份订单满足后,4天剩余的教室数分别为0,3,2,3。第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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: