• Luogu4587 [FJOI2016]神秘数


    题目大意:给定一个长度为$n$的正整数序列$a_i$,$m$次询问,每次询问$[l,r]$,求最小的无法表示成$a_l,a_{l+1},ldots,a_r$的子集之和的正整数。

    数据范围:$1leq lleq rleq nleq 10^5,1leq mleq 10^5,sum a_ileq 10^9$


    (FJOI考Codechef原题???)

    这个套路之前学校训练的时候也遇到过,这里又遇到了。。。

    我们考虑对于一组询问如何计算。

    首先,我们知道$[1,0]$是肯定可以表示出来的(为了方便表示就这样写了)

    可以像数学归纳法那样递推,先把所有数从小到大排序,假设$[1,x]$被计算出都是可以表示的,现在又有了一个新的数$v$。

    若$v>x+1$,那么$x+1$还是无法表示出来,还是只有$[1,x]$是可以的。

    若$vleq x+1$,那么$[1,x+v]$都是可以表示的。


    根据这个规律,我们这样考虑。

    设$s_0=0$,每次递推这样做($i=0,1,2,3,ldots$)。

    计算区间中$leq s_i+1$的数之和,设为$s_{i+1}$。

    反复进行,直到$s_i=s_{i+1}$,也就是无法再扩展这个区间了,答案就是$s_i+1$。

    (感性理解一下,应该就是对的)

    但是我们需要这样递推多少次呢?

    我们发现,$s_i$增长最慢的情况就是$s_0=0,s_1=1,s_2=2,s_3=3,s_4=5,s_5=8,ldots$,就是斐波那契数列,所以是指数级别增长的,所以在$log(sum a_i)$次以内就会停止。

    每次计算$leq s_i+1$的数之和的时候,可以用从小到大将数加入主席树,然后二分一下每次在哪个线段树中计算$[l,r]$中的和就可以了。

    时间复杂度为$O(nlog nlog(sum a_i))$

     1 #include<bits/stdc++.h>
     2 #define Rint register int
     3 using namespace std;
     4 const int N = 100003;
     5 int n, m, a[N], id[N], root[N], seg[N << 5], ls[N << 5], rs[N << 5], cnt;
     6 inline void pushup(int x){seg[x] = seg[ls[x]] + seg[rs[x]];}
     7 inline void change(int &now, int pre, int L, int R, int pos, int val){
     8     now = ++ cnt; seg[now] = seg[pre]; ls[now] = ls[pre]; rs[now] = rs[pre];
     9     if(L == R){
    10         seg[now] = val;
    11         return;
    12     }
    13     int mid = L + R >> 1;
    14     if(pos <= mid) change(ls[now], ls[pre], L, mid, pos, val);
    15     else change(rs[now], rs[pre], mid + 1, R, pos, val);
    16     pushup(now);
    17 }
    18 inline int query(int x, int L, int R, int l, int r){
    19     if(l <= L && R <= r) return seg[x];
    20     int mid = L + R >> 1, ans = 0;
    21     if(l <= mid) ans += query(ls[x], L, mid, l, r);
    22     if(mid < r) ans += query(rs[x], mid + 1, R, l, r);
    23     return ans;
    24 }
    25 inline int calc(int l, int r, int v){
    26     int left = 0, right = n, mid, ans;
    27     while(left <= right){
    28         mid = left + right >> 1;
    29         if(a[id[mid]] <= v) ans = mid, left = mid + 1;
    30         else right = mid - 1;
    31     }
    32     return query(root[ans], 1, n, l, r);
    33 }
    34 int main(){
    35     scanf("%d", &n);
    36     for(Rint i = 1;i <= n;i ++) scanf("%d", a + i), id[i] = i;
    37     sort(id + 1, id + n + 1, [](int x, int y) -> bool {return a[x] < a[y];});
    38     for(Rint i = 1;i <= n;i ++)
    39         change(root[i], root[i - 1], 1, n, id[i], a[id[i]]);
    40     scanf("%d", &m);
    41     while(m --){
    42         int l, r, s = 0, tmp = 0;
    43         scanf("%d%d", &l, &r);
    44         while(true){
    45             tmp = calc(l, r, s + 1);
    46             if(s == tmp) break;
    47             s = tmp;
    48         }
    49         printf("%d
    ", s + 1);
    50     }
    51 }
    Luogu4587
  • 相关阅读:
    嵌入式Linux学习笔记 NAND Flash控制器
    (嵌入式开发)自己写bootloader之编写第一阶段
    C_C++指针指针应用详解
    数据结构笔记-----二叉排序树和哈希表
    map方法和filter方法
    nginx服务器卡住了 解决办法
    vue-devtoools 调试工具安装
    web 本地存储(localStorage、sessionStorage)
    vux使用教程
    一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
  • 原文地址:https://www.cnblogs.com/AThousandMoons/p/10731461.html
Copyright © 2020-2023  润新知