题目链接: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;
}