P4587 [FJOI2016] 神秘数

P4587 [FJOI2016] 神秘数

暑假zdy集训让我感到人类智慧的一题
虽然本题貌似希望考察主席树,但是我觉得这题的Trick还是太智慧了

[FJOI2016] 神秘数

题目描述

一个可重复数字集合 \(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\)\(m\) 次询问,每次询问包含两个参数 \(l,r\),你需要求出由 \(a_l,a_{l+1},\cdots,a_r\) 所组成的可重集合的神秘数。

提示

对于 \(100\%\) 的数据点,\(1\le n,m\le {10}^5\)\(\sum a\le {10}^9\)

solution:

首先我们来思考一个问题:

假设在先前的集合中
[1,lim]已经被证明可取,现在新加入一个数x:

当x>lim+1:
显然lim+1这个数不可取,所以x对于答案无贡献

当x<=lim+1:

显然[1,lim+x]全部可取

lim+1由x+(lim+1-x)取得:

lim+2由x+(lim+2-x)取得

lim+3由x+(lim+3-x)取得...

然而新的极限显然就是x+lim了

但是我们发现,如果每一次对一个lim进行拓展操作,那么这个时间复杂度将会变为 \(n^2\)

所以我们考虑对于多个符合x<=lim的x进行拓展操作:

先说结论:记所有满足\(a_i<=x\)\(a_i\)的和为\(sum_x\)

只有当\(sum_x>lim\)时,lim可被更新为\(sum_x\)

否则直接结束,答案为lim+1

证:

显然,lim一定是由一些\(a_i\)<=lim的\(a_i\)组成的
如果\(sum[lim] <= lim\) ,说明lim+1不可能被取到

又因为[1,lim]已经被取到,且sum[lim]中的每个数都满足x<=lim,
所以由之前朴素的转移不难想到:

将sum[lim]分成两部分,pre和suf

其中pre用来组成[1,lim]

然后suf中的每个数都对lim由贡献

那么对于suf中的每个数,他们对lim贡献完之后,新的lim显然就是
pre中所有数字的和加上suf中所有数字的和,既为sum

然后不难想到主席树在这题中的应用:计算sum[x]

然后这题就做完了

Code:

#include<bits/stdc++.h>
const int N=1e5+5;
const int inf=1e9;
using namespace std;
int n,m,cnt;
int ls[N*4*20],rs[N*4*20],rt[N],ans[N];
int a[N],b[N];
struct Tree{
    int val,cnt;
}t[N*4*20];
int upd(int &x,int last,int l,int r,int k)
{
    x=++cnt;
    t[x]=t[last];
    ls[x]=ls[last];
    rs[x]=rs[last];
    t[x].val+=b[k];
    if(l==r)
    {
        return x;
    }
    int mid=l+r>>1;
    if(k<=mid)ls[x]=upd(ls[x],ls[last],l,mid,k);
    else rs[x]=upd(rs[x],rs[last],mid+1,r,k);
    return x;
}
void query(int x,int y,int l,int r,int pos,int &res)
{
    if(l==r)
    {
        res+=t[x].val-t[y].val;
        return ;
    }
    int mid=l+r>>1;
    if(mid<pos)
    {
        query(rs[x],rs[y],mid+1,r,pos,res);
        res+=t[ls[x]].val-t[ls[y]].val;
    }
    else
        query(ls[x],ls[y],l,mid,pos,res);
}
void work()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n);
    for(int i=1;i<=n;i++)
    {
        a[i]=lower_bound(b+1,b+1+n,a[i])-(b+1)+1;
        upd(rt[i],rt[i-1],1,n,a[i]);
    }
    cin>>m;
    for(int i=1,l,r;i<=m;i++)
    {
        scanf("%d%d",&l,&r);
        int x=1;
        while(1)
        {
            int val=0,x_id= x<=b[n] ? lower_bound(b+1,b+1+n,x)-(b+1) : n;
            if(x<=b[n]&&b[x_id+1]==x)x_id++;
            query(rt[r],rt[l-1],1,n,x_id,val);
            if(x<=val)x=val+1;
            else break;
        }
        printf("%d\n",x);
    }
}
int main()
{
    work();
    return 0;
}

posted @ 2024-12-06 11:52  liuboom  阅读(53)  评论(0)    收藏  举报