BZOJ 2006: [NOI2010]超级钢琴

Orz zzk
最直接的想法是找出所有不同的长度在[L,R]的子段然后选最大的k个加到答案中
但是太暴力没前途啊

zzk教会我:
首先构造前缀和数组pre
定义一个三元组MAX(i,L,R)表示以i为右端点且左端点在[L,R]之间的使pre[i]-pre[t-1]最大的值
(等价于[L,R]中使pre[t-1]最小的值) 其中(L<=t<=R)
显然ans就是k个最大的这样的三元组的值的和
可以用ST表+堆

首先枚举i,用ST表找到最小的pre[t] (作为初始状态) 放入一个堆中
然后进行k次查找:每次找到堆顶计入ans,然后因为需要选出的子段各不相同,把(i,L,R)拆成(i,L,t-1)和(i,t+1,r)放入堆中

#include<cstdio>
#include<algorithm>
#include<queue>
#define N 500005
#define RG register
#define LL long long
#define maxlog 20
using namespace std;

inline int read()
{
    int a=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
    return a*f;
}

int n,L,R,k;
LL ans;
int pre[N],Log[N+5],ST[N][maxlog+5];
struct node
{
    int i,l,r,t;
    node(int a,int b,int c,int d) {i=a;l=b;r=c;t=d;}
    bool operator < (const node &c) const
    {
        return pre[i]-pre[t-1]<pre[c.i]-pre[c.t-1];
    }
};

int MIN(int a,int b){return pre[a-1]<pre[b-1]?a:b;}

int query(int x,int y)
{
    int p=Log[y-x+1];
    return MIN(ST[x][p],ST[y-(1<<p)+1][p]);
}

priority_queue<node> heap;

int main()
{
    n=read(),k=read(),L=read(),R=read();
    RG int i,j;
    for(i=1;i<=n;++i) pre[i]=read()+pre[i-1],ST[i][0]=i;
    for(i=2;i<=n;++i) Log[i]=Log[i>>1]+1;
    for(j=1;(1<<j)<=n;++j)
        for(i=1;i<=n-(1<<j)+1;++i)
            ST[i][j]=MIN(ST[i][j-1],ST[i+(1<<j-1)][j-1]);
    for(i=L;i<=n;++i)
    {
        int l=max(i-R+1,1),r=i-L+1;
        heap.push(node(i,l,r,query(l,r)));
    }
    ans=0;
    for(i=1;i<=k;++i)
    {
        node x=heap.top();heap.pop();
        ans+=1ll*(pre[x.i]-pre[x.t-1]);
        if(x.t>x.l) heap.push(node(x.i,x.l,x.t-1,query(x.l,x.t-1)));
        if(x.t<x.r) heap.push(node(x.i,x.t+1,x.r,query(x.t+1,x.r)));
    }
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值