洛谷 P1083 借教室
https://www.cnblogs.com/violet-acmer/p/9721160.html
一、暴力简述
首先我们不难看出,这道题--并不是一道多难的题,因为显然,第一眼看题目时便很容易地想到暴力如何打:枚举每一种订单,然后针对每一种订单,对区间内的每一天进行修改(做减法),直到某一份订单使得某一天剩下的教室数量为负数,即可得出结果。
先小小的评析一下吧:凡是能打出几近正解的暴力题,都不是难题!
但是,显然枚举形式的暴力会很慢,期望的时间复杂度约为O(m \times n)O(m×n)。
二、思想详述
让我们开动脑筋想一下:每张订单其实就可以看作是一个区间(操作),左右区间分别为开始时间和结束时间,所以这不就是一个区间操作吗——首选线段tree啦!
先介绍一种好理解、好实现的算法:差分数组。
在介绍差分之前,需要介绍前缀和思想
我们有一组数(个数小于等于一千万),并且有一大堆询问——给定区间l、r,求l、r之间所有数之和(询问个数小于等于一千万)
此处暴力肯定不行啊(O(NQlength)),那么我们来观察前缀和是怎么做的:用sum[i]来存储前i个数的和,然后用sum[r]-sum[l-1]来表示l~r之间所有数的和。(l-1原因是l~r只看要包含l)而sum数组便可以通过简单的递推求出来
代码核心:
for(int i=;i<=n;i++)
{
cin>>a[i];
sum[i]=sum[i-]+a[i];
}
for(int i=;i<=q;i++)
{
cin>>l>>r;
cout<<sum[r]-sum[l-]<<" ";
}
而所谓的差分数组,即是前缀和数组的逆运算:
我们给定前i个数相邻两个数的差(1<=i<=n),求每一项a[i](1<=i<=n)。
此时无非就是用作差的方式求得每一项,此时我们可以有一个作差数组diff,diff[i]用于记录a[i]-a[i-1],然后对于每一项a[i],我们可以递推出来:
for(int i=;i<=n;i++)
{
cin>>diff[i];
a[i]=diff[i]+a[i-];
}
for(int i=;i<=n;i++)
{
cout<<a[i];
}
到这儿,我们可以看出来,前缀和是用元数据求元与元之间的并集关系,而差分则是根据元与元之间的逻辑关系求元数据,是互逆思想(qwq但是有时元数据和关系数据不是很好辨别或者产生角色反演啊)。
但是,理解了前缀和&差分,并不代表肯定能做到模板题。
三、关于答案二分
一般来说,二分是个很有用的优化途径,因为这样会直接导致减半运算,而对于能否二分,有一个界定标准:状态的决策过程或者序列是否满足单调性或者可以局部舍弃性。
而在这个题里,因为如果前一份订单都不满足,那么之后的所有订单都不用继续考虑;
而如果后一份订单都满足,那么之前的所有订单一定都可以满足,符合局部舍弃性,所以可以二分订单数量。
四、正解
首先,要明白如为什么要用区间差分而不是区间前缀和:因为这个题每次操作针对的对象都是原本题目中给的元数据,而不是让求某个关系,所以采用差分。
其次,要知道差分会起到怎样的作用:因为diff数组决定着每个元数据的变化大小、趋势,所以,当我们想要针对区间操作时,前缀和可以转化成对diff数组操作:
diff[l[i]] += d[i];
diff[r[i]+] -= d[i];//d[i]是指第 i 天要借的教室数
因为后面的元数据都由之前的diff数组推导出来,所以改变diff[i]就相当于改变 i 之后的每一个值,并通过重新减去改变的量,达到操作区间的目的。
then,我们需要想明白策略:从第一份订单开始枚举,直到无法满足或者全枚举完结束。
最后,一点提示,我下面的标程是通过比大小来判断是否满足,而不是作差判负数——能不出负数就别出负数。
以上解析来自大佬%%%%%%%:https://www.luogu.org/problemnew/solution/P1083
差分数组讲解--大佬博客:http://www.cnblogs.com/widsom/p/7750656.html
AC代码1:差分数组
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=1e6+; int n,m;
struct Node1
{
int l,r;
int d;
}ord[maxn];//订单 l : 开始时间 r : 结束时间 d : 每天需要的房间数
int rest[maxn];//rest[i] : 第 i 可以提供的房间数
int diff[maxn];//差分数组
int need[maxn];//need[i] : 第 i 天需要的房间数 bool isOk(int x)
{
memset(diff,,sizeof(diff));
for(int i=;i <= x;++i)
{
diff[ord[i].l] += ord[i].d;
diff[ord[i].r+] -= ord[i].d;
}
for(int i=;i <= n;++i)
{
need[i]=need[i-]+diff[i];
if(need[i] > rest[i])
return false;
}
return true;
} int main()
{
scanf("%d%d",&n,&m);
for(int i=;i <= n;++i)
scanf("%d",rest+i);
for(int i=;i <= m;++i)
scanf("%d%d%d",&ord[i].d,&ord[i].l,&ord[i].r); if(isOk(m))
{
printf("0\n");
return ;
}
int l=,r=m;
while(l < r)//二分查找答案
{
int mid=l+((r-l)>>);
if(isOk(mid))
l=mid+;
else
r=mid;
}
printf("%d\n%d\n",-,r);
}
线段树解法:
看题解前要确保会线段树区间更新的模板题(懒惰标记)以及用线段树解决RMQ问题的模板题。
变量解释:
rest[ i ] : 第 i 天可以提供的房间数
flag : 判断某订单是否满足条件,初始为false
定义的线段树结构体:
struct Node1
{
int l,r;
int val;
int lazy;
int mid(){
return l+((r-l)>>);
}
bool isEqual(){
return l == r ? true:false;
}
}segTree[*maxn];
l,r : 左右区间
lazy : 懒惰标记,此处不再是表示懒惰的次数,而是表示在当前节点懒惰的值
val : 如果节点v是非叶节点,则其存储的是左右儿子中的val值较小的值,叶节点存储的是第 i 天可以提供的房间数
题解:
线段树的节点存储的是区间最小值,每次区间更新时判断懒惰的区间是否满足条件 (订单需要的房间数 <= 可以提供的房间数),如果不满足,令 flag = true,结束更新操作。
因为区间更新操作是顺次执行第一份订单到最后一份订单,所以第一个使flag == true的订单一定是第一个不满足条件的订单,输出此订单编号。
具体细节看代码。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ls(x) ((x)<<1)
#define rs(x) ((x)<<1 | 1)
const int maxn=1e6+; int n,m;
int rest[maxn];
bool flag;
//==============线段树============
struct Node1
{
int l,r;
int val;
int lazy;
int mid(){
return l+((r-l)>>);
}
bool isEqual(){
return l == r ? true:false;
}
}segTree[*maxn];
void pushUp(int pos)//向上更新,左右孩子的最小值
{
segTree[pos].val=min(segTree[ls(pos)].val,segTree[rs(pos)].val);
}
void buildTree(int l,int r,int pos)
{
segTree[pos].l=l,segTree[pos].r=r;
segTree[pos].lazy=;
if(segTree[pos].isEqual())
{
segTree[pos].val=rest[l];
return ;
}
int mid=l+((r-l)>>);
buildTree(l,mid,ls(pos));
buildTree(mid+,r,rs(pos));
pushUp(pos);
}
void pushDown(int pos)//向下更新
{
if(segTree[pos].lazy > )//不能再懒惰了
{
int lazy=segTree[pos].lazy;
segTree[ls(pos)].lazy += lazy;
segTree[rs(pos)].lazy += lazy;
segTree[ls(pos)].val -= lazy;
segTree[rs(pos)].val -= lazy;
segTree[pos].lazy=;
}
}
void update(int l,int r,int val,int pos)
{
if(l == segTree[pos].l && r == segTree[pos].r)
{
segTree[pos].lazy += val;
if(segTree[pos].val < val)//判断是否满足条件
{
flag=true;//如果不能提供足够的房间,令 flag = true ,结束更新
return ;
}
segTree[pos].val -= val;
pushUp(pos>>);
return ;
}
pushDown(pos);
int mid=segTree[pos].mid();
if(r <= mid)
update(l,r,val,ls(pos));
else if(l > mid)
update(l,r,val,rs(pos));
else
{
update(l,mid,val,ls(pos));
update(mid+,r,val,rs(pos));
}
pushUp(pos);
}
//================================ int main()
{
scanf("%d%d",&n,&m);
for(int i=;i <= n;++i)
scanf("%d",rest+i);
buildTree(,n,);
flag=false;
for(int i=;i <= m;++i)
{
int d,s,t;
scanf("%d%d%d",&d,&s,&t);
update(s,t,d,);
if(flag)
{
printf("%d\n%d\n",-,i);//输出第一个使 flag = true 的订单编号
return ;
}
}
printf("0\n");
return ;
}
洛谷 P1083 借教室的更多相关文章
- 洛谷P1083 借教室
P1083 借教室 题目描述 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借 ...
- 【题解】洛谷 P1083 借教室
目录 题目 思路 \(Code\) 题目 P1083 借教室 思路 线段树.需要的操作为区间修改,区间查询.维护每个区间的最小值就好. \(Code\) #include<iostream> ...
- 洛谷 P1083 借教室 题解
P1083 借教室 题目描述 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借 ...
- [NOIP2012] 提高组 洛谷P1083 借教室
题目描述 在大学期间,经常需要租借教室.大到院系举办活动,小到学习小组自习讨论,都需要向学校申请借教室.教室的大小功能不同,借教室人的身份不同,借教室的手续也不一样. 面对海量租借教室的信息,我们自然 ...
- 洛谷P1083借教室题解
题目 这个难度感觉并没有那么高,因为这个题暴力也好打,但是比较难想出正解,因为如果你不看标签是很难想到这个题竟然是二分,当然前缀和应该很好想,毕竟让你求的是在某段时间内借教室的和是否满足. 这样我们可 ...
- 洛谷 P1083 借教室【二分+差分/线段树】
二分mid,然后用1~mid的操作在差分序列上加减,最后把差分序列前缀和起来,看是否有有超过初始r值的 #include<iostream> #include<cstdio> ...
- VIjos——V 1782 借教室 | | 洛谷——P1083 借教室
https://vijos.org/p/1782|| https://www.luogu.org/problem/show?pid=1083 描述 在大学期间,经常需要租借教室.大到院系举办活动,小到 ...
- 『题解』洛谷P1083 借教室
更好的阅读体验 Portal Portal1: Luogu Portal2: LibreOJ Portal3: Vijos Description 在大学期间,经常需要租借教室.大到院系举办活动,小到 ...
- NOIP2012 洛谷P1083 借教室
传送门 题意:有一些学(xian)生(quan)要借教室.在n天内,第i天学校有ri个教室.有m份订单,每份订单有三个数值dj,sj,tj,分别表示这个订单从第sj天开始到第tj天结束(包括端点),每 ...
- 洛谷P1083 借教室 题解
题目 [NOIP2012 提高组] 借教室 题解 这道题是几周之前做到的一道题,本来不想讲的,因为这道题也是用到了二分答案的方法,这类题目之前已经发布过两篇题解了.但这道题还运用了差分数组这个思想,所 ...
随机推荐
- python基础学习笔记(一)
安装与运行交互式解释器 在绝大多数linux和 UNIX系统安装中(包括Mac OS X),Python的解释器就已经存在了.我们可以在提示符下输入python命令进行验证(作者环境ubuntu) f ...
- main函数是必须的吗
研究实验4 研究过程: 问题引出:C语言编程非得用主函数main吗,不用是否可以? 对此问题进行研究,用tc.exe书写代码如下: 图1 没有main函数的c程序 对其进行编译,链接发现,编译阶段可 ...
- 第九次Scrum meeting
第九次Scrum meeting 任务及完成度: 成员 12.31 1.1 陈谋 任务1040:完成stackoverflow的数据处理后的json处理(99%) 任务1114-1:完成对网页数据的 ...
- 2-Twenty First Scrum Meeting-20151221
任务安排 成员 今日完成 明日任务 闫昊 请假(数据库) 唐彬 请假(数据库) 史烨轩 尝试使用downloadmanager对notification进行更新 尝试使用downloadm ...
- 《Linux内核分析》第四周学习总结
<Linux内核分析>第四周学习总结 ——扒开系统调用的三层皮 姓名:王玮怡 学号:20135116 理论总结部分: 第一节 用户态.内核 ...
- Linux内核分析 读书笔记 (第五章)
第五章 系统调用 5.1 与内核通信 1.调用在用户空间进程和硬件设备之间添加了一个中间层.该层主要作用有三个: 为用户空间提供了硬件的抽象接口. 系统调用保证了系统的稳定和安全. 实现多任务和虚拟内 ...
- 软件分析之QQ
腾讯QQ(简称“QQ”)是腾讯公司开发的一款基于Internet的即时通信软件.腾讯QQ支持在线聊天.视频通话.点对点断点续传文件.共享文件.网络硬盘.自定义面板.QQ邮箱等多种功能,并可与多种通讯终 ...
- Sprint 冲刺第三阶段第一天
1.今晚我在整理之前的代码,检查细节,然后发现游戏要返回上一界面竟然出现了问题“项目停止运行”,仔细检查没办法解决,后来百度可能是因为修改了之前文件的名字,可在AndroidManifest.xml中 ...
- mysql外键关联
主键:是唯一标识一条记录,不能有重复的,不允许为空,用来保证数据完整性 外键:是另一表的主键, 外键可以有重复的, 可以是空值,用来和其他表建立联系用的.所以说,如果谈到了外键,一定是至少涉及到两张表 ...
- PAT 1019 数字黑洞
https://pintia.cn/problem-sets/994805260223102976/problems/994805302786899968 给定任一个各位数字不完全相同的4位正整数,如 ...