【NOI2010T2】超级钢琴-主席树+优先队列

来源:互联网 发布:wifi网络测试工具 编辑:程序博客网 时间:2024/04/29 16:32

测试地址:超级钢琴
做法:题目要求的是,给定数列中长度在LR之间的连续子段中,前K大的子段和之和。乍一看十分复杂,我们可以慢慢分析。
实际上我们可以一个一个地提取出前K大的子段,怎么提取呢?先考虑提取最大的子段,我们可以求对于每一个i,以第i个元素结尾的最大的满足要求的子段和,这个怎么求呢?对数列求一个前缀和,令sum(i)=a(1)+...+a(i),那么要求以第i个元素结尾的最大的满足要求的子段和,实际上就是求sum(i)sum(iL),sum(i)sum(iL1),...,sum(i)sum(iR)中的最大值,这个最大值等于sum(i)减去sum(iL),sum(iL1),...,sum(iR)中的最小值,求区间最小值显然可以用线段树维护。这样,我们对于每一个i求出这样的值,然后里面最大的就是整个数列中满足要求的最大子段和。
取出了最大子段和后,要求次大子段和了,这回要怎么办呢?其实我们可以在求最大子段和的时候排除掉已经取掉的子段,假设这个子段以a(i)结尾,那么去掉这个子段后,以a(i)结尾的最大子段和是什么呢?那就是原来以a(i)结尾的次大子段和。也就是说,在原来那一堆值里,我们去掉了一个值,又要添加以a(i)结尾的次大子段和进那一堆值,然后再求最大值取出。扩展到取第K段,我们取第K1段之后,我们紧接着就要求以这一段结尾元素结尾的下一个最大的段,因此我们必须解决求区间内第任意小值的问题,然后将它的值插入到备选的位置中,满足插入、查询最小值和删除最小值的数据结构显然就是堆了,也可以用STL中的优先队列来写。那么我们只需要解决求区间第任意小的问题了,这个显然可以用主席树处理,这个问题就解决了,总的时间复杂度大约是O((N+K)logN)
注意在使用主席树的时候,要开够空间,不然会RE或WA(只是显示,实质上还是RE)。
以下是本人代码:

#include <cstdio>#include <cstdlib>#include <cstring>#include <iostream>#include <algorithm>#include <queue>#define ll long longusing namespace std;int n,K,L,R,tot=0,a[500010],pos[500010],rt[500010],t[500010];ll sum[500010];int seg[20000010],lft[20000010],rht[20000010];struct forsort {int val,id;} f[500010];struct state{    ll val;    int id;    bool operator < (state a) const    {        return val<a.val;    }};priority_queue <state> Q;bool cmp(forsort a,forsort b){    return a.val<b.val;}void buildtree(int &no,int l,int r){    no=++tot;    seg[no]=0;    if (l==r) return;    int mid=(l+r)>>1;    buildtree(lft[no],l,mid);    buildtree(rht[no],mid+1,r);}void add(int &no,int last,int l,int r,int x){    no=++tot;    seg[no]=seg[last],lft[no]=lft[last],rht[no]=rht[last];    if (l==r)    {        seg[no]++;        return;    }    int mid=(l+r)>>1;    if (x<=mid) add(lft[no],lft[last],l,mid,x);    else add(rht[no],rht[last],mid+1,r,x);    seg[no]=seg[lft[no]]+seg[rht[no]];}int query(int l,int r,int k){    if (k>seg[rt[r]]-seg[rt[l-1]]) return -1;    int last=rt[l-1],no=rt[r],x=1,y=n+1;    while(x!=y)    {        int mid=(x+y)>>1;        if (seg[lft[no]]-seg[lft[last]]>=k) y=mid,no=lft[no],last=lft[last];        else x=mid+1,k-=seg[lft[no]]-seg[lft[last]],no=rht[no],last=rht[last];    }    return f[x].id-1;}void work1(){    scanf("%d%d%d%d",&n,&K,&L,&R);    f[1].id=1,f[1].val=0;    sum[0]=0;    for(int i=1;i<=n;i++)    {        scanf("%d",&a[i]);        f[i+1].id=i+1,f[i+1].val=f[i].val+a[i];        sum[i]=(ll)f[i+1].val;    }    sort(f+1,f+n+2,cmp);    for(int i=1;i<=n+1;i++)    {        pos[f[i].id]=i;    }    buildtree(rt[0],1,n+1);    for(int i=1;i<=n+1;i++) add(rt[i],rt[i-1],1,n+1,pos[i]);}void work2(){    state next;    for(int i=L;i<=n;i++)    {        t[i]=1;        int p=query(max(i-R+1,1),i-L+1,t[i]);        next.val=sum[i]-sum[p],next.id=i;        Q.push(next);    }    ll ans=0;    while(K--)    {        state now=Q.top();Q.pop();        ans+=now.val;        t[now.id]++;        int p=query(max(now.id-R+1,1),now.id-L+1,t[now.id]);        if (p!=-1)        {            next.val=sum[now.id]-sum[p],next.id=now.id;            Q.push(next);        }    }    printf("%lld",ans);}int main(){    work1();    work2();    return 0;}
原创粉丝点击