NOIP 2012 提高组 借教室(vijos 1782) 总览


            附:题目链接                      vijos 1782                                        地址:          https://vijos.org/p/1782


            本题的关键就是对区间值得修改,从而判定是否满足条件

 


 

        一、如何快速的对区间值进行修改?

              很容易想到处理该类问题的线段树打法

                 并且针对该题,线段树的数组表示的应为该区间所有天中能借教室数量的最小值,因为最小值够借出则该区间符合要求,

         并且还要打上tag,作为下传的量;但是  这样的时间复杂度还是很高的。vijos上只能过85。

 


 

        二、于是引出本题的正解:二分 + 差分;

             I 、常规二分~~~~~~~~~~~~~~~~~~~~~~~~~

                            保证   第一个要通知的人 在 l 到 r 的区间内,

                            所以   r 要取 n + 1;(如果最后该人的位置是n + 1,则意味供应满足所有需求~)

                            1、  每次取mid = (l + r) shr 1

                            2、  开一个 need 数组 记录 差分数列(need[1]=a[1]-a[0] ( a[0]=0 ),need[2]=a[2]-a[1],need[3]=

                                                                                                           a[3]-a[2]......need[n]=a[n]-a[n-1])

                                  根据差分数列的性质,只要修改起始值和终点值就可以完成对一段区间的修改:

                                        将第 i 个人的申请添入满足只要 inc(  need[ s[ i ] ] , d[ i ])  

                                        因为这将影响后面所有值,

                                        所以还要dec( need[ t[ i ] +1 ] , d[ i ]);(关于s,t,d数组是什么看题目链接);     

 

                           2、 将前 mid 个添加进差分数列

                                 再从第 2 个开始 令 need[ i ] := need[ i ] + need[ i - 1 ]; //即 将 差分 变为 原数组; 执行need[ i ]

                                 时need [ i - 1 ]的值已经是a[ i - 1 ]

                                 //则 need [ i ] + a[ i - 1 ] = a[ i ] - a[ i - 1 ] + a[ i - 1 ] = a[ i ];不断上传

                           3、 如果前 mid天 第 i 天的需求大于 r[ i ](该天供应) 则 r 变为 mid

                                                              小于等于 r[ i ](该天供应) 则 l  变为 mid+1

                                                            (之所以要+1是因为该天满足要求,不在考虑区间内~)     

                           4、 警告!!need的数组做一次就要清零一次,因为它是一个累加的数组,并不是直接赋值的!!

                           5、 边界自己处理(根据l 到 r 区间表示的意思不同边界也不同);

                                 后面的文章有AC程序,主要是vijos数据较弱 -  =,别的地方测会 超时 几个点,

                                 但 可以优化,优化后也能过~ 还有常规二分也有其他打法~这里都不再做赘述~

 

              II、特殊二分~~~~~~~~~~~~~~~~~~~~~~~~

                            即针对特殊题目的不同于二分标准结构的打法,由于是针对,所以时效快了近一倍~(¥ 。¥)~啦啦啦~

                           分析:

                                常规二分之所以时效低主要是因为不断的清零need的数组的对一个点的多次无意义赋值:

                                比如 第一天 ,如果到最后 供过于求 即 从 l=1 ;     r=n+1;

                                                                                  到 l=n+1 ; r=n+1;

                                一共做了 log ( 2 ,n ) 次,单单第一个点就重复添加进need数组log( 2 , n )次并且不断清零,相当

                           于把一个东西删掉,在写入,这是完全没必要的。

                           特殊二分过程: 

                    记当前要处理的区间为 l ,r ,再增加一个指标 f 来判定要在当前区间内添加还是删除( f = 1 为 增加 否则 为 删除 )

                                如 开始 l = 1; r = n; f = 1;

                                同二分过程,取 mid = (l + r) shr 1 

                                因为 f=1 则  将 l 至 r  这段区间   内 的  所有需求   添加进    need数组(need和常规二分的差分数组need一样)

                                一样从 1 扫到 n:

                                   i:=1;

                                   o:=0;

                                   while (i <= n) and (need[ i ] + o <= offer[ i ]) do begin

                                     o:=need[ i ]+o;

                                     i:=i+1;

                                   end;

                                和常规二分的不同,常规二分做完无论如何都要清零need数组,但在这用o来传递 a[i] 的值,need数组不变

                                如果 i >= n 那么说明 分配的区间 过大 ,也就是说 l 到 m 这段区间不能全部满足,

                                那么此时就执行 删除过程:

                                    r  缩进为 mid

                                    将 f 赋成 -1 代表:要、在 l 到 r 的区间内、删除、后某些数,直到满足要求

                                f = -1 删除过程:

                                    取 mid = ( l + r ) shr 1

                                    将 mid 到 r 区间的数删除 重复直到 不超过;

              III、中心思想~~~~~~~~~~~~~~~~~~~~~~~~

                              从本质上说这种方法就是先取区间内前一半

                                                                       ||

                                                                       \/

                                         如果没满出来,那么重复执行从剩下的区间再取前一半          

                                                                       ||                                               

                                                                       \/                                                       

                                          如果满出来,那么从上一个取的区间内前一半的区间            

                                          去删除掉后一半区间的数,重复执行直到又没满出来,

                                                               再执行上一步              

 

 

                                          直到该区间没数为止(可能还剩下一个,标程会提到)

                                   复杂度分析

                                          从理论上来讲,大多数点只执行了 1 到 2 次(最多就添加一次,发现满出后被删除)

                                          need数组也不用一次次清零,时效比常规快了很多,具有针对的二分20个点测下来

                                          也能比优化后的二分快0.5秒左右~ ¥  ¥

                                   详见标程~


 

                    总的来说,有三种打法:


                        线段树85分打法
                             优点:打起来容易,代码不长;
                             缺点:在本题中时效不如二分高,空间不如二分优;
                             综合:略微堪忧;


                        常规二分 
                             优点:按普通二分结构可快速写出代码,边界好判断;
                             缺点:不具有针对性使得在时效上难以突出,重复多;
                             综合:较弱的数据比线段树略快,但大数据也会超时;


                        根据该题修改后的二分
                             优点:空间优,时效高;
                             缺点:比要在常规二分上修改,不同的打法边界考虑也有差;
                             综合:比上面两种好;


 

                 END~~~~~~~~~~~~~~~~~~~~~~~~~~~~

posted @ 2013-10-16 13:35  哥少先队的  阅读(648)  评论(3编辑  收藏  举报