zoukankan      html  css  js  c++  java
  • 【NOI2010T2】超级钢琴-主席树+优先队列

    测试地址:超级钢琴
    做法:题目要求的是,给定数列中长度在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 long
    using 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;
    }
    
  • 相关阅读:
    [转]Go语言中的make和new
    Python中的get和set方法
    协程是个啥玩意
    聊聊Python中的is和==
    聊聊Python中的闭包和装饰器
    聊聊Python中的生成器和迭代器
    聊聊动态语言那些事(Python)
    在mac上安装svn客户端
    关于mac mini组装普液晶显示器
    IOS 日志输出控制
  • 原文地址:https://www.cnblogs.com/Maxwei-wzj/p/9793590.html
Copyright © 2011-2022 走看看