[Bzoj 4299][主席树]ForbiddenSum

我们可以得到一个充分必要条件:
我们记vi为区间内小于等于i的数的和
那么答案为第一个i,满足vi<i
如果我们现在求得at,val
其中val是区间内小于等于at的数的和
那么对于小于等于val的i,都可以被某个子集和表示出来
于是,我们可以转移到
(val,小于等于val的数的和)
这时如果val==小于等于val的数的和
我们可以转移到(val+1,小于等于val+1的数的和)
如果小等于val+1的数的和<val+1,那我们就输出val+1,break
这其中查询一个区间内小于等于某数的和可以用主席树来实现
可以看出,at增长很快,是log级别的…
然后我不会证复杂度…

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m;
#define Maxn 100010
#define V 6000010
int a[Maxn];
struct Node{
    int ls,rs;
    int sumv;
}tree[V];
int root[Maxn];
int cnt=0;
void insert(int &k,int x,int pos,int l,int r){
    k=++cnt;
    tree[k]=tree[x];
    tree[k].sumv+=pos;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(pos<=mid)insert(tree[k].ls,tree[x].ls,pos,l,mid);
    else insert(tree[k].rs,tree[x].rs,pos,mid+1,r);
}
int Query(int k1,int k2,int l,int r,int L,int R){
    if(l==L&&r==R)return tree[k2].sumv-tree[k1].sumv;
    int mid=(l+r)>>1;
    if(R<=mid)return Query(tree[k1].ls,tree[k2].ls,l,mid,L,R);
    else if(mid<L)return Query(tree[k1].rs,tree[k2].rs,mid+1,r,L,R);
    else return Query(tree[k1].ls,tree[k2].ls,l,mid,L,mid)+Query(tree[k1].rs,tree[k2].rs,mid+1,r,mid+1,R); 
}

inline void rd(int &x){
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
}

int main(){
    rd(n);
    for(register int i=1;i<=n;++i){
        rd(a[i]);
        insert(root[i],root[i-1],a[i],1,1e9);
    }
    rd(m);
    int l,r;
    for(register int tt=1;tt<=m;++tt){
        rd(l);rd(r);
        int at=1,val=Query(root[l-1],root[r],1,1e9,1,1);
        while(at<=val){
            at=val;
            val=Query(root[l-1],root[r],1,1e9,1,at);
            if(at==val){
                at++;
                if(at<=1000000000)val=Query(root[l-1],root[r],1,1e9,1,at);
            }
        }
        printf("%d\n",at);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ezoilearner/article/details/82939819