题解:[NOI2010] 超级钢琴
题意分析
求前 \(k\) 大的答案之和,考虑多路归并。
设 \(a\) 的前缀和为 \(\textit{pre}\)。
设计状态 \((s,l,r)\),表示区间左端点为 \(s\),右端点在 \([l,r]\)。
则有:
\[\textit{ans}(s,l,r)=\max_{i=l}^r\textit{pre}_i-\textit{pre}_{s-1}
\]
因此只需要求 \([l,r]\) 内的 \(\textit{pre}_i\) 最大值,ST 表即可 \(\mathcal O(1)\) 维护。
\(L,R\) 的限制即:
\[i+L-1\leq l\leq r\leq\min(i+R-1,n)
\]
因为左端点 \(s\) 一定时,可以确定右端点在 \([l,r]\) 内的最优右端点 \(p\)。之后把状态分裂成 \((s,l,p-1),(s,p+1,r)\)。这两个状态一定不会比 \((s,l,r)\) 更优。因此对于每一个 \(i\) 求出初始状态,之后丢进大根堆里多路归并即可。
实际是线上还需要注意要保证 \(l\leq p-1,p+1\leq r\)。(因为分裂后区间最大长度不会增加,最小长度不会减小,所以不用考虑 \(L,R\) 的限制。)
时间复杂度 \(\mathcal O((n+k)\log n)\)。
AC 代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
typedef long long ll;
constexpr const int N=5e5,K=5e5;
int n,k,L,R,a[N+1];
struct ST{
pair<int,int> st[N+1][__lg(N+1)+1];
void build(int a[],int n){
for(int i=1;i<=n;i++){
st[i][0]={a[i],i};
}
for(int i=1;(1<<i)<=n;i++){
for(int x=1;x+(1<<i)-1<=n;x++){
st[x][i]=max(st[x][i-1],st[x+(1<<i-1)][i-1]);
}
}
}
pair<int,int> query(int l,int r){
int s=__lg(r-l+1);
return max(st[l][s],st[r-(1<<s)+1][s]);
}
}st;
struct node{
int s,l,r;
int value(){
return st.query(l,r).first-a[s-1];
}
int pos(){
return st.query(l,r).second;
}
};
bool operator <(node a,node b){
return a.value()<b.value();
}
int main(){
/*freopen("test.in","r",stdin);
freopen("test.out","w",stdout);*/
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k>>L>>R;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]+=a[i-1];
}
st.build(a,n);
priority_queue<node>q;
for(int i=1;i<=n-L+1;i++){
q.push({i,i+L-1,min(i+R-1,n)});
}
ll ans=0;
while(q.size()&&k){
node x=q.top();
q.pop();
ans+=x.value();
k--;
int p=x.pos();
if(L<=p-x.s&&x.l<=p-1){
q.push({x.s,x.l,p-1});
}
if(p+1<=x.r){
q.push({x.s,p+1,x.r});
}
}
cout<<ans<<'\n';
cout.flush();
/*fclose(stdin);
fclose(stdout);*/
return 0;
}

浙公网安备 33010602011771号