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;
}