神秘数

神秘数

时间限制: 1 Sec 内存限制: 256 MB
题目描述
一个可重复数字集合S的神秘数定义为最小的不能被S的子集的和表示的正整数。例如S={1,1,1,4,13},
1 = 1
2 = 1+1
3 = 1+1+1
4 = 4
5 = 4+1
6 = 4+1+1
7 = 4+1+1+1
8无法表示为集合S的子集的和,故集合S的神秘数为8。
现给定n个正整数a[1]..a[n],m个询问,每次询问给定一个区间l,r,求由a[l],a[l+1],…,a[r]所构成的可重复数字集合的神秘

输入
第一行一个整数n,表示数字个数。
第二行n个整数,从1编号。
第三行一个整数m,表示询问个数。
以下m行,每行一对整数l,r,表示一个询问。

输出
对于每个询问,输出一行对应的答案。

样例输入
5
1 2 4 9 10
5
1 1
1 2
1 3
1 4
1 5

样例输出
2
4
8
8
8

【数据范围】
对于10%的数据点,n,m <= 10
对于30%的数据点,n,m <= 1000
对于60%的数据点,n,m <= 50000
对于100%的数据点,n,m <= 100000,∑a[i] <= 109

来源
FJOI2016

题解

写在前面:就是这道题害我省选爆零,直接退役。
进入正题:
考虑一段数列,如何计算它的神秘数?
将其排序,若sum[i-1]>s[i]+1,那么神秘数即为sum[i-1]。
对于一组询问,每次暴力更新区间内>=sum+1的数,当出现斐波那契数列时达到上界。
用一棵主席树维护区间和。
注意细节,记得对拍,不然就要像我一样爆零退役了。

代码

#include<iostream> 
#include<cstdio> 
#include<cstdlib> 
#include<cstring> 
#include<string> 
#include<cmath> 
#include<algorithm> 
#define N 100010
#define inf 2100000000
using namespace std;
int n,m,tot,s[N],id[N],num[N],rt[N],cnt;
struct node{int lc,rc,sum;}t[N*18];
bool cmp(const int &a,const int &b){return s[a]<s[b];}

class functional_seg_tree
{
  public:
  void modify(int &x,int pre,int l,int r,int des,int val)
  {
    x=++cnt;t[x]=t[pre];t[x].sum+=val;
    if(l==r)return;
    int mid=l+r>>1;
    if(des<=mid)modify(t[x].lc,t[pre].lc,l,mid,des,val);
    else modify(t[x].rc,t[pre].rc,mid+1,r,des,val);
  }
  int qry(int x,int l,int r,int ql,int qr)
  {
    if(ql<=l&&r<=qr)return t[x].sum;
    int mid=l+r>>1,res=0;
    if(ql<=mid)res+=qry(t[x].lc,l,mid,ql,qr);
    if(qr>mid)res+=qry(t[x].rc,mid+1,r,ql,qr);
    return res;
  }
}T;

int main()
{
  int a,b;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)scanf("%d",&s[i]),id[i]=i;
  sort(id+1,id+n+1,cmp);
  for(int i=1;i<=n;i++)
  {
    num[++tot]=s[id[i]];
    T.modify(rt[tot],rt[tot-1],1,n,id[i],s[id[i]]);
    while(s[id[i]]==s[id[i+1]])
      i++,T.modify(rt[tot],rt[tot],1,n,id[i],s[id[i]]);
  }

  scanf("%d",&m);
  while(m--)
  {
    scanf("%d%d",&a,&b);
    int ans=0,pre=0;
    while(1)
    {
      int l=1,r=tot,pos=0;
      while(r-l>1)
      {
        int mid=l+r>>1;
        if(num[mid]<=ans+1)l=mid;
        else r=mid-1;   
      }
      pos=num[r]<=ans+1?pos=r:l;
      if(pos==pre)break;
      ans=T.qry(rt[pre=pos],1,n,a,b);
    }
    printf("%d\n",ans+1);
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值