zoukankan      html  css  js  c++  java
  • 洛谷 P1083 借教室

    传送门:Probem 1083

    https://www.cnblogs.com/violet-acmer/p/9721160.html

    一、暴力简述

      首先我们不难看出,这道题--并不是一道多难的题,因为显然,第一眼看题目时便很容易地想到暴力如何打:枚举每一种订单,然后针对每一种订单,对区间内的每一天进行修改(做减法),直到某一份订单使得某一天剩下的教室数量为负数,即可得出结果。

      先小小的评析一下吧:凡是能打出几近正解的暴力题,都不是难题!

      但是,显然枚举形式的暴力会很慢,期望的时间复杂度约为O(m imes 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数组便可以通过简单的递推求出来

      代码核心:

     1 for(int i=1;i<=n;i++)
     2 {
     3     cin>>a[i];
     4     sum[i]=sum[i-1]+a[i];
     5 }
     6 for(int i=1;i<=q;i++)
     7 {
     8     cin>>l>>r;
     9     cout<<sum[r]-sum[l-1]<<" ";
    10 }
    View Code

      而所谓的差分数组,即是前缀和数组的逆运算:

      我们给定前i个数相邻两个数的差(1<=i<=n),求每一项a[i](1<=i<=n)。

      此时无非就是用作差的方式求得每一项,此时我们可以有一个作差数组diff,diff[i]用于记录a[i]-a[i-1],然后对于每一项a[i],我们可以递推出来:

    1 for(int i=1;i<=n;i++)
    2 {
    3     cin>>diff[i];
    4     a[i]=diff[i]+a[i-1];
    5 }
    6 for(int i=1;i<=n;i++)
    7 {
    8     cout<<a[i];
    9 }
    View Code

      到这儿,我们可以看出来,前缀和是用元数据求元与元之间的并集关系,而差分则是根据元与元之间的逻辑关系求元数据,是互逆思想(qwq但是有时元数据和关系数据不是很好辨别或者产生角色反演啊)。

      但是,理解了前缀和&差分,并不代表肯定能做到模板题。

    三、关于答案二分

      一般来说,二分是个很有用的优化途径,因为这样会直接导致减半运算,而对于能否二分,有一个界定标准:状态的决策过程或者序列是否满足单调性或者可以局部舍弃性。

       而在这个题里,因为如果前一份订单都不满足,那么之后的所有订单都不用继续考虑;

      而如果后一份订单都满足,那么之前的所有订单一定都可以满足,符合局部舍弃性,所以可以二分订单数量。

    四、正解

      首先,要明白如为什么要用区间差分而不是区间前缀和:因为这个题每次操作针对的对象都是原本题目中给的元数据,而不是让求某个关系,所以采用差分。

      其次,要知道差分会起到怎样的作用:因为diff数组决定着每个元数据的变化大小、趋势,所以,当我们想要针对区间操作时,前缀和可以转化成对diff数组操作:

    1 diff[l[i]] += d[i];
    2 diff[r[i]+1] -= d[i];//d[i]是指第 i 天要借的教室数
    View Code

      因为后面的元数据都由之前的diff数组推导出来,所以改变diff[i]就相当于改变 i 之后的每一个值,并通过重新减去改变的量,达到操作区间的目的。

      then,我们需要想明白策略:从第一份订单开始枚举,直到无法满足或者全枚举完结束。

      最后,一点提示,我下面的标程是通过比大小来判断是否满足,而不是作差判负数——能不出负数就别出负数。

      以上解析来自大佬%%%%%%%:https://www.luogu.org/problemnew/solution/P1083  

      差分数组讲解--大佬博客:http://www.cnblogs.com/widsom/p/7750656.html

    AC代码1:差分数组

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 using namespace std;
     5 const int maxn=1e6+50;
     6 
     7 int n,m;
     8 struct Node1
     9 {
    10     int l,r;
    11     int d;
    12 }ord[maxn];//订单 l : 开始时间 r : 结束时间 d : 每天需要的房间数
    13 int rest[maxn];//rest[i] : 第 i 可以提供的房间数
    14 int diff[maxn];//差分数组
    15 int need[maxn];//need[i] : 第 i 天需要的房间数
    16 
    17 bool isOk(int x)
    18 {
    19     memset(diff,0,sizeof(diff));
    20     for(int i=1;i <= x;++i)
    21     {
    22         diff[ord[i].l] += ord[i].d;
    23         diff[ord[i].r+1] -= ord[i].d;
    24     }
    25     for(int i=1;i <= n;++i)
    26     {
    27         need[i]=need[i-1]+diff[i];
    28         if(need[i] > rest[i])
    29             return false;
    30     }
    31     return true;
    32 }
    33 
    34 int main()
    35 {
    36     scanf("%d%d",&n,&m);
    37     for(int i=1;i <= n;++i)
    38         scanf("%d",rest+i);
    39     for(int i=1;i <= m;++i)
    40         scanf("%d%d%d",&ord[i].d,&ord[i].l,&ord[i].r);
    41 
    42     if(isOk(m))
    43     {
    44         printf("0
    ");
    45         return 0;
    46     }
    47     int l=1,r=m;
    48     while(l < r)//二分查找答案
    49     {
    50         int mid=l+((r-l)>>1);
    51         if(isOk(mid))
    52             l=mid+1;
    53         else
    54             r=mid;
    55     }
    56     printf("%d
    %d
    ",-1,r);
    57 }
    View Code

    线段树解法:

      看题解前要确保会线段树区间更新的模板题(懒惰标记)以及用线段树解决RMQ问题的模板题。

    变量解释:

      rest[ i ] : 第 i 天可以提供的房间数

      flag : 判断某订单是否满足条件,初始为false

    定义的线段树结构体:

     1 struct Node1
     2 {
     3     int l,r;
     4     int val;
     5     int lazy;
     6     int mid(){
     7         return l+((r-l)>>1);
     8     }
     9     bool isEqual(){
    10         return l == r ? true:false;
    11     }
    12 }segTree[4*maxn];
    View Code

      l,r : 左右区间

      lazy : 懒惰标记,此处不再是表示懒惰的次数,而是表示在当前节点懒惰的值

      val : 如果节点v是非叶节点,则其存储的是左右儿子中的val值较小的值,叶节点存储的是第 i 天可以提供的房间数

      

    题解:

      线段树的节点存储的是区间最小值,每次区间更新时判断懒惰的区间是否满足条件 (订单需要的房间数 <= 可以提供的房间数),如果不满足,令 flag = true,结束更新操作。

      因为区间更新操作是顺次执行第一份订单到最后一份订单,所以第一个使flag == true的订单一定是第一个不满足条件的订单,输出此订单编号。

      具体细节看代码。

      1 #include<iostream>
      2 #include<cstdio>
      3 #include<cstring>
      4 using namespace std;
      5 #define ls(x) ((x)<<1)
      6 #define rs(x) ((x)<<1 | 1)
      7 const int maxn=1e6+50;
      8 
      9 int n,m;
     10 int rest[maxn];
     11 bool flag;
     12 //==============线段树============
     13 struct Node1
     14 {
     15     int l,r;
     16     int val;
     17     int lazy;
     18     int mid(){
     19         return l+((r-l)>>1);
     20     }
     21     bool isEqual(){
     22         return l == r ? true:false;
     23     }
     24 }segTree[4*maxn];
     25 void pushUp(int pos)//向上更新,左右孩子的最小值
     26 {
     27     segTree[pos].val=min(segTree[ls(pos)].val,segTree[rs(pos)].val);
     28 }
     29 void buildTree(int l,int r,int pos)
     30 {
     31     segTree[pos].l=l,segTree[pos].r=r;
     32     segTree[pos].lazy=0;
     33     if(segTree[pos].isEqual())
     34     {
     35         segTree[pos].val=rest[l];
     36         return ;
     37     }
     38     int mid=l+((r-l)>>1);
     39     buildTree(l,mid,ls(pos));
     40     buildTree(mid+1,r,rs(pos));
     41     pushUp(pos);
     42 }
     43 void pushDown(int pos)//向下更新
     44 {
     45     if(segTree[pos].lazy > 0)//不能再懒惰了
     46     {
     47         int lazy=segTree[pos].lazy;
     48         segTree[ls(pos)].lazy += lazy;
     49         segTree[rs(pos)].lazy += lazy;
     50         segTree[ls(pos)].val -= lazy;
     51         segTree[rs(pos)].val -= lazy;
     52         segTree[pos].lazy=0;
     53     }
     54 }
     55 void update(int l,int r,int val,int pos)
     56 {
     57     if(l == segTree[pos].l && r == segTree[pos].r)
     58     {
     59         segTree[pos].lazy += val;
     60         if(segTree[pos].val <  val)//判断是否满足条件
     61         {
     62             flag=true;//如果不能提供足够的房间,令 flag = true ,结束更新
     63             return ;
     64         }
     65         segTree[pos].val -= val;
     66         pushUp(pos>>1);
     67         return ;
     68     }
     69     pushDown(pos);
     70     int mid=segTree[pos].mid();
     71     if(r <= mid)
     72         update(l,r,val,ls(pos));
     73     else if(l > mid)
     74         update(l,r,val,rs(pos));
     75     else
     76     {
     77         update(l,mid,val,ls(pos));
     78         update(mid+1,r,val,rs(pos));
     79     }
     80     pushUp(pos);
     81 }
     82 //================================
     83 
     84 int main()
     85 {
     86     scanf("%d%d",&n,&m);
     87     for(int i=1;i <= n;++i)
     88         scanf("%d",rest+i);
     89     buildTree(1,n,1);
     90     flag=false;
     91     for(int i=1;i <= m;++i)
     92     {
     93         int d,s,t;
     94         scanf("%d%d%d",&d,&s,&t);
     95         update(s,t,d,1);
     96         if(flag)
     97         {
     98             printf("%d
    %d
    ",-1,i);//输出第一个使 flag = true 的订单编号
     99             return 0;
    100         }
    101     }
    102     printf("0
    ");
    103     return 0;
    104 }
    View Code

      

  • 相关阅读:
    微信小程序UI组件、开发框架、实用库...
    关于Web中的图标使用问题
    通过jQuery Ajax使用FormData对象上传文件
    css3 动画demo
    css知识汇总
    git初体验(三)git分支
    js数组去重的三种常用方法总结
    JavaScript继承方式详解
    面试题连接收集
    城市列表的实例
  • 原文地址:https://www.cnblogs.com/violet-acmer/p/9721088.html
Copyright © 2011-2022 走看看