P1083 借教室

40 篇文章 0 订阅
16 篇文章 0 订阅

https://www.luogu.com.cn/problem/P1083

题目描述

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

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

我们需要处理接下来nn天的借教室信息,其中第ii天学校有r_iri​个教室可供租借。共有mm份订单,每份订单用三个正整数描述,分别为d_j,s_j,t_jdj​,sj​,tj​,表示某租借者需要从第s_jsj​天到第t_jtj​天租借教室(包括第s_jsj​天和第t_jtj​天),每天需要租借d_jdj​个教室。

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

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

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

输入格式

第一行包含两个正整数n,mn,m,表示天数和订单的数量。

第二行包含nn个正整数,其中第ii个数为r_iri​,表示第ii天可用于租借的教室数量。

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

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

输出格式

如果所有订单均可满足,则输出只有一行,包含一个整数00。否则(订单无法完全满足)

输出两行,第一行输出一个负整数-1−1,第二行输出需要修改订单的申请人编号。

输入输出样例

输入 #1复制

4 3 
2 5 4 3 
2 1 3 
3 2 4 
4 2 4

输出 #1复制

-1 
2

说明/提示

【输入输出样例说明】

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

【数据范围】

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

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

对于 70%的数据,有1 ≤ n,m ≤ 10^51≤n,m≤105;

对于 100%的数据,有1 ≤ n,m ≤ 10^6,0 ≤ r_i,d_j≤ 10^9,1 ≤ s_j≤ t_j≤ n1≤n,m≤106,0≤ri​,dj​≤109,1≤sj​≤tj​≤n

NOIP 2012 提高组 第二天 第二题


一个操作就是对一段区间进行全部– <–>借用教室,当借用完了对一段区间++ <—>归还教室;对一段区间的操作转化到两个点去操作 <–>使用差分。但是这题刚开始让我疑惑的是差分是个离线操作,怎么算完一次知道什么时候不行呢..

实际上我们对当前执行到第几个教室就进行差分的整合,看当前有没有需要的教室》已经有的教室,这样就直接输出不满足的订单了。但是O(n)未免会TLE;我们这里用二分进行优化

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
typedef long long LL;
LL cnt[maxn],rest[maxn],l[maxn],r[maxn],need[maxn],d[maxn];
LL n,m;
LL ask(LL x)///问到第x个订单能否满足 
{
	memset(cnt,0,sizeof(cnt));
	for(LL i=1;i<=x;i++)
	{
	 	cnt[l[i]]+=d[i];
		cnt[r[i]+1]-=d[i];	
	}		
	for(LL i=1;i<=n;i++)
	{
		need[i]=need[i-1]+cnt[i];
		if(need[i]>rest[i]) return 1;
	}
 return 0;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);	
  cin>>n>>m;
  for(LL i=1;i<=n;i++)  cin>>rest[i];
  for(LL i=1;i<=m;i++)
  {
  	cin>>d[i]>>l[i]>>r[i];
  }
  	LL left=1;LL right=m+1;
  	//二分找到满足和不满足的边界 
  	while(left<right)
  	{
  		LL mid=(left+right)>>1;
		if(ask(mid)) right=mid;
		else left=mid+1;	
	}
	if(left==m+1) cout<<0<<endl;
	else
	{
		cout<<-1<<endl;
		cout<<left<<endl;
	}
return 0;
}


当然这题也可以用线段树做。不过注意要吸氧(不然就T两个点)

把[l,r]的借教室看成是区间修改,打lazy标记,然后维护区间最小值,每一次借教室减去已有教室中一定的数目,下一次借教室的时候看区间最小值是不是《需要的教室数量,此时就不满足输出-1和订单号

#include<iostream>
#include<vector>
#include<queue>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=1e6+10;
typedef long long LL;
struct SeamentTree
{
	LL l,r;
	LL val,add;//区间最小值和区间修改
	#define l(x) tree[x].l
	#define r(x) tree[x].r
	#define val(x) tree[x].val
	#define add(x) tree[x].add 
}tree[maxn*4];
LL a[maxn],n,m;
void build(LL p,LL l,LL r)
{
	l(p)=l,r(p)=r;
	if(l==r) {val(p)=a[l];return;}
	LL mid=(l+r)>>1;
	if(l<=mid) build(p*2,l,mid);
	if(mid<r) build(p*2+1,mid+1,r);
	val(p)=min(val(p*2),val(p*2+1)); 
}
void spread(LL p)
{
	if(add(p))
	{
		val(p*2)-=add(p);
		val(p*2+1)-=add(p);
		add(p*2)+=add(p);
		add(p*2+1)+=add(p);
		add(p)=0;
	}
}
void change(LL p,LL l,LL r,LL d)
{
	if(l<=l(p)&&r>=r(p))
	{
		val(p)-=d;
		add(p)+=d;
		return;
	}
	spread(p);
	LL mid=(l(p)+r(p))>>1;
	if(l<=mid) change(p*2,l,r,d);
	if(r>mid) change(p*2+1,l,r,d);
	val(p)=min(val(p*2),val(p*2+1)); 
}
LL ask(LL p,LL l,LL r)
{
	if(l<=l(p)&&r>=r(p))
	{
		return val(p);
	}
	spread(p);
	LL mid=(l(p)+r(p))/2;
	LL val=0x3f3f3f3f3f3f3f;
	if(l<=mid) val=min(val,ask(p*2,l,r));
	if(r>mid)  val=min(val,ask(p*2+1,l,r));
	return val;
}
int main(void)
{
  cin.tie(0);std::ios::sync_with_stdio(false);
  LL n,m;cin>>n>>m;
  for(LL i=1;i<=n;i++) cin>>a[i];
  build(1,1,n);
  int flag=1;
  for(LL i=1;i<=m;i++)
  {
  	 LL l,r,d;
  	 cin>>d>>l>>r;
  	 if(ask(1,l,r)<d) 
  	 {
  	 	cout<<-1<<endl<<i<<endl;
	    flag=0;
		break;
	 }
	 change(1,l,r,d); 
  }
  if(flag)
  cout<<0<<endl;
return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值