[NOI2010] 超级钢琴
一个很经典的做法。
\(k\)个最值的求法,先对每个元素进行最操作,再对堆顶进行次操作丢入堆。
考虑怎么进行次操作。
每次把可操作的区间按最操作的位置切开。
#include<iostream>
#include<cstdio>
#include<queue>
#define ll long long
#define N 500005
inline ll read(){
ll ans = 0,f = 1;
char a = getchar();
while((a != '-') && !(a <= '9' && a >= '0'))
a = getchar();
if(a == '-')
f = -1,a = getchar();
while(a <= '9' && a >='0')
ans = (ans << 3) + (ans << 1) + (a - '0'),a = getchar();
return ans * f;
}
ll num[N],sum[N];
ll n,k,l,r;
ll v[N << 2];
ll s[N << 2];
struct P{
int li,ri,t,x,v;
};
std::priority_queue<P>QWQ;
bool operator < (P a,P b){
return a.v < b.v;
}
#define ls(x) (x << 1)
#define rs(x) (x << 1 | 1)
#define mid ((l + r) >> 1)
inline void build(int u,int l,int r){
if(l == r){
v[u] = sum[l];
s[u] = l;
return ;
}
build(ls(u),l,mid);
build(rs(u),mid + 1,r);
v[u] = std::min(v[ls(u)],v[rs(u)]);
if(v[u] == v[ls(u)])
s[u] = s[ls(u)];
else
s[u] = s[rs(u)];
}
inline ll q(int u,int l,int r,int tl,int tr){
if(tl <= l && r <= tr)
return u;
ll ans1 = 0 , ans2 = 0;
if(tl <= mid)
ans1 = q(ls(u),l,mid,tl,tr);
if(tr > mid)
ans2 = q(rs(u),mid + 1,r,tl,tr);
if(v[ans1] < v[ans2])
return ans1;
else
return ans2;
}
int main(){
v[0] = 1e18;
scanf("%lld%lld%lld%lld",&n,&k,&l,&r);
for(int i = 1;i <= n;++i)
num[i] = read(),sum[i] = sum[i - 1] + num[i];
build(1,0,n);
for(int i = l;i <= n;++i){
P a;
a.li = std::max((ll)0,i - r);
a.ri = i - l;
a.t = s[q(1,0,n,a.li,a.ri)];
a.x = i;
a.v = sum[i] - sum[a.t];
QWQ.push(a);
}
// for(int i = 1;i <= n;++i)
// std::cout<<sum[i]<<" ";
// puts("");
ll ans = 0;
for(int i = 1;i <= k;++i){
P a = QWQ.top();
QWQ.pop();
ans += a.v;
P b;
b.li = a.li;
b.ri = a.t - 1;
if(b.li <= b.ri){
b.t = s[q(1,0,n,b.li,b.ri)];
b.x = a.x;
b.v = sum[b.x] - sum[b.t];
QWQ.push(b);
}
b.li = a.t + 1;
b.ri = a.ri;
if(b.li <= b.ri){
b.t = s[q(1,0,n,b.li,b.ri)];
b.x = a.x;
b.v = sum[b.x] - sum[b.t];
QWQ.push(b);
}
}
std::cout<<ans<<std::endl;