【BZOJ4408】神秘数(FJOI2016)-主席树+贪心

测试地址:神秘数
题目大意:定义一个集合的神秘数,为不能用其某个子集的和表示的正整数中最小的数,给定一个长为 n 的序列 A m 次询问,每次询问某个区间的神秘数。 n , m 10 5 , A i 10 9
做法:本题需要用到主席树+贪心。
考虑只有一个询问时要怎么做。首先,将 A i 从小到大一个个加入集合,那么在某个时刻,能用集合的某个子集表示的数的集合在数轴上就不是连续的了,我们知道这时候,最小的断点就是我们要求的神秘数。为什么呢?令在这个时刻之前,能用它们表示的数的集合为 1 ~ x ,加上 A i 后,能表示的数的集合为 1 ~ x A i ~ x + A i 的并集,如果 A i > x + 1 ,就会产生 x + 1 这个断点,而因为对于任意 j > i ,有 A j A i ,所以无论如何都拼不出 x + 1 来了,于是这个 x + 1 就是集合的神秘数。
注意到这样做一次最多是 O ( n ) 的,考虑优化。因为我们要求第一个 A i 使得 A i > 1 + j = 1 i 1 A j ,每当我们添加进一个数 A i ,答案一定会大于 i = 1 n A i ,这样我们只需找到比这个大的第一个 A i 进行下一步即可,而中间的数可以直接累加进答案里。可以证明这样只会进行 O ( log A i ) 步,简单证明如下:设某一步的前缀和为 x ,那么下一步我们至少会加上比 x 大的最小的 A i ,这样总和会成倍增长,所以复杂度为上面所述。
注意到,上面询问中,寻找序列区间中比某数大的第一个数,询问序列区间中权值在某一段内的数的和,这两个显然是主席树的基本操作,因此用主席树维护,时间复杂度为 O ( m log n log A i )
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
int n,m,tmp,a[100010],pos[100010],rt[100010];
int seg[2000010]={0},ch[2000010][2]={0},tot=0;
struct forsort
{
    int id,val;
}f[100010];

bool cmp(forsort a,forsort b)
{
    return a.val<b.val;
}

void buildtree(int &no,int l,int r)
{
    no=++tot;
    if (l==r) return;
    int mid=(l+r)>>1;
    buildtree(ch[no][0],l,mid);
    buildtree(ch[no][1],mid+1,r);
}

void insert(int &no,int last,int l,int r,int x)
{
    no=++tot;
    seg[no]=seg[last];
    ch[no][0]=ch[last][0];
    ch[no][1]=ch[last][1];
    if (l==r) {seg[no]+=pos[x];return;}
    int mid=(l+r)>>1;
    if (x<=mid) insert(ch[no][0],ch[last][0],l,mid,x);
    else insert(ch[no][1],ch[last][1],mid+1,r,x);
    seg[no]=seg[ch[no][0]]+seg[ch[no][1]];
}

int query(int no,int last,int x)
{
    int s=0,l=1,r=tmp;
    while(l<r)
    {
        int mid=(l+r)>>1;
        if (pos[mid]<=x)
        {
            s+=seg[ch[no][0]]-seg[ch[last][0]];
            no=ch[no][1];
            last=ch[last][1];
            l=mid+1;
        }
        else
        {
            no=ch[no][0];
            last=ch[last][0];
            r=mid;
        }
    }
    if (pos[l]<=x) s+=seg[no]-seg[last];
    return s;
}

int find(int no,int last,int l,int r,int x)
{
    if (seg[no]-seg[last]==0) return -1;
    if (pos[r]<=x) return -1;
    if (l==r) return l;
    int mid=(l+r)>>1,ans=-1;
    if (x<pos[mid]) ans=find(ch[no][0],ch[last][0],l,mid,x);
    if (ans==-1) ans=find(ch[no][1],ch[last][1],mid+1,r,x);
    return ans;
}

int solve(int L,int R)
{
    int x=0;
    while(true)
    {
        int s,nxt;
        s=query(rt[R],rt[L-1],x);
        nxt=find(rt[R],rt[L-1],1,tmp,x);
        x=s;
        if (nxt==-1||pos[nxt]>x+1) break;
        x+=pos[nxt];
    }
    return x+1;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&f[i].val);
        f[i].id=i;
    }

    sort(f+1,f+n+1,cmp);
    tmp=0;
    for(int i=1;i<=n;i++)
    {
        if (i==1||f[i].val!=f[i-1].val)
            pos[++tmp]=f[i].val;
        a[f[i].id]=tmp;
    }

    buildtree(rt[0],1,tmp);
    for(int i=1;i<=n;i++)
        insert(rt[i],rt[i-1],1,tmp,a[i]);

    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        int l,r;
        scanf("%d%d",&l,&r);
        printf("%d\n",solve(l,r));
    }

    return 0;
}

猜你喜欢

转载自blog.csdn.net/maxwei_wzj/article/details/80685108