【贪心+ST算法+堆】BZOJ2006(NOI2010)[超级钢琴]题解

39 篇文章 2 订阅
11 篇文章 0 订阅

题目概述

给出一个序列,选出 k k 个长度在 [L,R] 的子段(不可选重),求 k k 个子段的和的最大值。

解题报告

2017.8.8Update:好像因为这种贪心套路特别经典就被冠名超级钢琴了233333。

如果我们把所有长度在 [L,R] 的子段都处理出来并从大到小排序,那么根据贪心,肯定选前 k k 个最优秀。

但我们不可能把所有满足要求的子段都处理出来:太多了。需要注意到的是, k 并不是很大,所以我们要想办法每次都选最大的满足要求的子段, 选 k k 次累加起来就是答案。

定义 MAX(i,l,r)=max{sum(i)sum(t1)|ltr} ,即以 i i 为右端点,左端点范围在 [l,r] 之间的最大子段, sum s u m 是前缀和。我们考虑一个位置 i i MAX(i,max(iR+1,1),iL+1) 是以 i i 为右端点的初始最优解,那么从所有初始最优解中刷出最大的就是第一大的满足子段。

假设第一大的三元组是 (i,l,r) ,最优解位置在 t t ,那么由于 t 已经被选了,所以 [l,r] [ l , r ] 被拆成了 [l,t1] [ l , t − 1 ] [t+1,r] [ t + 1 , r ] ,把 (i,l,t1) ( i , l , t − 1 ) (i,t+1,r) ( i , t + 1 , r ) 加入待选三元组。不停地从待选三元组中选出 MAX M A X 最大的三元组,并加入新产生的三元组,选 k k 次即可。

已经有了想法,接下来我们只需要解决两个问题即可:

  1. 如何快速求出 MAX(i,l,r) :由于 i i 固定,所以用ST算法预处理区间最小值即可。
  2. 如何快速选出最大的 MAX :用堆即可。

    示例程序

    #include<cstdio>
    #include<cmath>
    #include<queue>
    using namespace std;
    typedef long long LL;
    const int maxn=500000,Log=19;
    
    int n,K,L,R,sum[maxn+5],RMQ[maxn+5][Log+5];
    LL ans;
    
    inline bool Eoln(char ch) {return ch==10||ch==13||ch==EOF;}
    inline char readc()
    {
        static char buf[100000],*l=buf,*r=buf;
        if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
        if (l==r) return EOF; else return *l++;
    }
    inline int readi(int &x)
    {
        int tot=0,f=1;char ch=readc(),lst='+';
        while ('9'<ch||ch<'0') {if (ch==EOF) return EOF;lst=ch;ch=readc();}
        if (lst=='-') f=-f;
        while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=readc();
        return x=tot*f,Eoln(ch);
    }
    int Miner(int i,int j) {if (sum[i-1]<sum[j-1]) return i; else return j;}
    void make_RMQ()
    {
        for (int j=1,k=log2(n);j<=k;j++)
        for (int i=1;i<=n-(1<<j)+1;i++)
            RMQ[i][j]=Miner(RMQ[i][j-1],RMQ[i+(1<<j-1)][j-1]);
    }
    int Ask(int L,int R) {int j=log2(R-L+1);return Miner(RMQ[L][j],RMQ[R-(1<<j)+1][j]);}
    struct data
    {
        int i,L,R,t;
        data(int a,int b,int c,int d) {i=a;L=b;R=c;t=d;}
        bool operator < (const data &c) const {return sum[i]-sum[t-1]<sum[c.i]-sum[c.t-1];}
    };
    priority_queue<data> Heap;
    int main()
    {
        freopen("program.in","r",stdin);
        freopen("program.out","w",stdout);
        readi(n);readi(K);readi(L);readi(R);
        for (int i=1,x;i<=n;i++) readi(x),sum[i]=sum[i-1]+x,RMQ[i][0]=i;make_RMQ();
        while (!Heap.empty()) Heap.pop();
        for (int i=L;i<=n;i++)
        {
            int l=max(i-R+1,1),r=i-L+1;
            Heap.push(data(i,l,r,Ask(l,r)));
        }
        while (K--)
        {
            data now=Heap.top();Heap.pop();
            ans+=sum[now.i]-sum[now.t-1];
            if (now.t>now.L) Heap.push(data(now.i,now.L,now.t-1,Ask(now.L,now.t-1)));
            if (now.t<now.R) Heap.push(data(now.i,now.t+1,now.R,Ask(now.t+1,now.R)));
        }
        printf("%lld\n",ans);
        return 0;
    }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值