[2017纪中10-21]Fantasy 优先队列+ST表

12 篇文章 0 订阅
3 篇文章 0 订阅

题目链接:https://jzoj.net/senior/#main/show/5409
原题。。。同NOI2010超级钢琴。
这种求n个集合最大的k的值,考虑先把每个集合中的最大值扔到一个公共堆里,若某个集合最大值还未从堆中取出,那么这个集合中的次大值就没有必要进堆。从堆中不断取k次即可。假设我们很容易得到每个集合的最大值,并且删除最大值很容易得到次大值等等,这个方法就很可行了。
这题先前缀和一下,对于相同右端点x的区间看成一个集合,那么这些区间的左端点-1就是区间[x-R,x-L]。最大值就是A[x]-min(A[k]){x-R<=k<=x-L},用ST表O(1)求最区间最小值。每次从堆中取出一个值后,把该点所在区间[l,r](该点在p)分成[l,p-1],[p+1,r]再扔进堆里即可。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#define ll long long
using namespace std;
const int maxn=100010;
int n,k,L,R,a[maxn];
ll ans=0;
struct node
{
    int t,p,l,r;
}st[maxn][20];
bool operator <(node a,node b){return a.t<b.t;}
priority_queue <node ,vector<node > > Q;
node query(int l,int r)
{
    int t=(int)(log(r-l+1)/log(2));
    return min(st[l][t],st[r-(1<<t)+1][t]);
}
void init()
{
    for(int i=0;i<=n;i++)
        {st[i][0].t=a[i];st[i][0].p=i;}
    for(int k=1;(1<<k)<=n;k++)
        for(int i=0;i<=n;i++)
            if(i+(1<<(k-1))>n) st[i][k]=st[i][k-1];
            else st[i][k]=min(st[i][k-1],st[i+(1<<(k-1))][k-1]);
}
int main()
{
    scanf("%d%d%d%d",&n,&k,&L,&R);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        a[i]+=a[i-1];
    }
    init();
    for(int i=L;i<=n;i++)
    {
        int lx=max(0,i-R),rx=i-L;
        node t=query(lx,rx);
        t.l=lx;t.r=rx;t.t=a[i]-t.t;
        Q.push(t);
    }
    for(int i=1;i<=k;i++)
    {
        node t=Q.top(),z;Q.pop();
        ans+=t.t;
        if(t.l<t.p) 
        {
            z=query(t.l,t.p-1);
            z.t=a[t.p]+t.t-z.t;
            z.l=t.l;z.r=t.p-1;
            Q.push(z);
        }
        if(t.p<t.r)
        {
            z=query(t.p+1,t.r);
            z.t=a[t.p]+t.t-z.t;
            z.l=t.p+1;z.r=t.r;
            Q.push(z);
        }
    }
    printf("%lld",ans);             
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值