• 纪中OJ 2019.01.25【NOIP提高组】模拟 B 组 T2 数字对


    声明


    数字对

     Time Limits: 2000 ms    Memory Limits: 262144 KB

      Description

        小 H 是个善于思考的学生,现在她又在思考一个有关序列的问题。 
        她的面前浮现出一个长度为 n 的序列 {ai},她想找出一段区间 [L, R] (1 <= L <= R <= n)。
        这个特殊区间满足,存在一个 k (L <= k <= R),并且对于任意的 i (L <= i <= R),ai 都能被 ak 整除。这样的一个特殊区间 [L, R] 价值为 R - L。
        小H想知道序列中所有特殊区间的 最大价值 是多少,而有多少个这样的区间呢?这些区间又分别是哪些呢?你能帮助她吧。
     

     

      Input

        第一行,一个整数 n.
        第二行,n 个整数,代表 ai.
     

     

      Output

        第一行两个整数,num 和 val,表示价值最大的特殊区间的个数以及最大价值。
        第二行 num 个整数,按升序输出每个价值最大的特殊区间的 L.
     

     
     
      Sample Input
     
        输入1:
         5
         4  6  9  3  6
     
        输入2:
         5
         2  3  5  7  11
     

     
      Sample Output
        
        输出1:
         1  3
         2
     
        输出2:
         5  0
         1  2  3  4  5
     

     

      Data Constraint

        30%: 1 <= n <= 30 , 1 <= ai <= 32.
        60%: 1 <= n <= 3000 , 1 <= ai <= 1024.
        80%: 1 <= n <= 300000 , 1 <= ai <= 1048576.
        100%: 1 <= n <= 500000 , 1 <= ai < 2 ^ 31.
     
     

     

        最近发现有用暴力等水法A掉这题的,如从头到尾枚举 ak ,然后再向两边搜索的水法。

      然而如果所有的 ai 都相同的话,时间复杂度就为O(n2),完全过不了。

      如:

      500000

      后面 500000 个 1

     

      此时时间复杂度为O(5000002),不知道运行到猴年马月,直接卡掉水法。

     

      接下来看正解(可能有其他更简单的方法,但这种方法绝对不会被卡掉!): 二分+rmq+hash

     

     
      分析:
        
        看到了最大价值,即求最优解,脑海中立马想到了:贪心、dp、优化的搜索(或暴力)和 二分答案 。显然,此题是在序列中操作,数据范围大,贪心条件不满足,明显的二分答案!!!
        于是用二分答案求出序列的最大长度,但如何判断答案 mid 的可行性和计算序列个数与开头位置呢?
     
        这样,问题就转化成如何判断某个区间是否存在:对于任意的 i (L <= i <= R),ai 都能被 ak 整除 了。
        显然,搜索是最普遍的选择,那这里我就讲讲搜索的优化方案:
     
        或许你们会问:不仅要询问每个区间(O(n)),又要找出其中有没有 ak 存在(O( n2)),时间复杂度不是 n吗,怎么过???
     

     
      优化方案:
      
        优化一:
     
          对于任意的 i (L <= i <= R),ai 都能被 ak 整除 中的 ak,是很好求出来的,就是区间几个数的最大公约数(gcd),在询问前预处理一下,线上调用即可。
          但是,对于一般的预处理(除前缀和和其他玄学预处理外),复杂度都是O(n2)的,一样过不了(哭笑不得~)。。。
     

          但是,在序列的维护中,自然想到 线段树、树状数组、rmq 啦~(对于前两项此题应该也能维护 gcd 吧?!~)
          我们了解过的 rmq 都是维护最大最小值的,以其速度快,范围广而出名。此题,一样可以维护区间的 gcd
     
          举个栗子:  4  6  9
          若已求出前两个数的 gcd 为2(rmq[1,1]=2),后一个数的 gcd 为 9(rmq[3,0]=a[3]=9),那区间的 gcd 就是 gcd(2,9)=1 啦~
     
          预处理时间复杂度降到 O(n log2 n)啦,在搜索时直接 O(1)的复杂度就能算出区间最小公约数(和 rmq 求最大最小值一样的,换成 gcd 了而已)。
     
        优化二:
          
          通过优化一算出区间的 ak 后,我们就要开始找区间内有没有这个 ak 了~
     
          然而我们发现,每个区间的搜索 O(n)不可省,而找 ak 又要 O(n),那岂不是 O(n2)吗(虽然达不到,但也接近了,加上预处理和二分答案等,绝对过不了)
          于是进一步优化:
     
          毕竟我们只找一个数 ak,很容易想到出现一个数就在 flag 数组打个标记,找有没有 ak 时直接判断 flag[ak] 是否打过标记就行了。
          而区间的移动一次的话,就 O(1)把新的那个数打一个标记,把出区间的那个删掉标记即可~
     
          此过程时间复杂度将至 O(1),效率极高,是在某个区间搜某些数时的常用方法,可以经常拿来用。
     
        优化三:
          光有优化二是不行的,因为它用空间换时间,会 MLE 的。(毕竟 ak 可达 20 多亿,数组开得了那么多吗???)
          所以,在用了优化二后,常常用这种方法解决空间问题:哈希表~(它俩基本绑定了,哈希是利用无用的空间存有用的数。不会的自己学,不必多说)
     
          但是,这里的哈希有三种模式: 1. 查询某数在哈希表中的位置(用于删标记);  2. 把某数存进哈希表(用于打标记);  3. 查询某数是否存在(用于查询区间内是否有 ak)。
     
          于是,我们用哈希表的稀少时间换了大量的空间~~~
          

     
      所以总体说,预处理 O(n log2 n),二分 O(log2 n*check),在 check 中,区间询问 O(n),找 ak O(hash)(hash 是哈希的时间复杂度,常数级别)
     
      这样,我们就把 O(n3)的时间复杂度将至 O(n log2 n+n log2 n*hash),粗略说就是 O(n log2 n),只是有点常数而已,不用卡常都能过(毕竟 2 秒时限)~~~
      
      下附标程,我就不写注释了~
        
           
     1 uses math;
     2 const
     3         mo=1000001;
     4 var
     5         l,r,mid,i,n,j,t,cnt:longint;
     6         a,b,h,ans:array[0..1000001] of longint;
     7         rmq:array[0..500001,0..21] of longint;
     8 function gcd(x,y:longint):longint;
     9 begin
    10         if y=0 then exit(x) else exit(gcd(y,x mod y));
    11 end;
    12 function hash(x,mode:longint):boolean;
    13 var
    14         k:longint;
    15 begin
    16         k:=x mod mo;
    17         while (h[k]<>0) and (h[k]<>x) do
    18         begin
    19                 inc(k);
    20                 if k>mo then k:=1;
    21         end;
    22         if mode=2 then cnt:=k else
    23                 if mode=1 then h[k]:=x else
    24                         if h[k]=x then exit(true) else exit(false);
    25 end;
    26 function check(x:longint):boolean;
    27 var
    28         l,t,j,flag,r:longint;
    29 begin
    30         fillchar(h,sizeof(h),0);
    31         for i:=1 to x do
    32                 hash(a[i],1);
    33         flag:=0;
    34         for l:=1 to n-x+1 do
    35         begin
    36                 hash(a[l-1],2);
    37                 h[cnt]:=0;
    38                 r:=l+x-1;
    39                 hash(a[r],1);
    40                 t:=trunc(ln(r-l+1)/ln(2));
    41                 j:=gcd(rmq[l,t],rmq[r-(1<<t)+1,t]);
    42                 if hash(j,0) then
    43                 begin
    44                         flag:=1;
    45                         inc(ans[x]);
    46                         b[ans[x]]:=l;
    47                 end;
    48         end;
    49         if flag=0 then exit(false) else exit(true);
    50 end;
    51 begin
    52         readln(n);
    53         for i:=1 to n do
    54         begin
    55                 read(a[i]);
    56                 rmq[i,0]:=a[i];
    57         end;
    58         readln;
    59         t:=trunc(ln(n)/ln(2))+1;
    60         for j:=1 to t do
    61                 for i:=1 to n do
    62                         if i+(1<<j)-1<=n then
    63                                 rmq[i,j]:=gcd(rmq[i,j-1],rmq[i+(1<<(j-1)),j-1]);
    64         l:=1;                               // l,r,mid 存的是区间长度
    65         r:=n;
    66         while l<r do
    67         begin
    68                 mid:=(l+r) div 2+1;
    69                 if check(mid) then l:=mid else r:=mid-1;
    70         end;
    71         if ans[l]=0 then
    72         begin
    73                 writeln(n,' 0');
    74                 for i:=1 to n-1 do
    75                         write(i,' ');
    76                 writeln(n);
    77                 halt;
    78         end;
    79         writeln(ans[l],' ',l-1);            //要输出价值,就是长度-1
    80         for i:=1 to ans[l]-1 do
    81                 write(b[i],' ');
    82         writeln(b[ans[l]]);
    83 end.
    标程
     
  • 相关阅读:
    JavaScript 代码简洁之道
    SpringBoot究竟是如何跑起来的?
    JavaScript是如何工作的: Web推送通知的机制
    Fundebug后端Java异常监控插件更新至0.2.0,支持Spring及Maven
    Maven入门教程
    浏览器缓存机制
    JavaScript是如何工作的:Service Worker的生命周期及使用场景
    深入浅出浏览器渲染原理
    JavaScript是如何工作的:Web Workers的构建块 + 5个使用他们的场景
    ASP.NET Core MVC中URL和数据模型的匹配
  • 原文地址:https://www.cnblogs.com/t-s-y/p/10320623.html
Copyright © 2020-2023  润新知