P2048 [NOI2010] 超级钢琴 题解
求一次前缀和,区间和转化为差值。
枚举左端点,对于当前左端点,有一个范围的右端点合法,可以将所有右端点插入堆中,贪心取前 \(k\) 个,时间复杂度 \(O(n^2)\)。
由于 \(k\) 不大,所以没必要将所有可行解都计算出来。固定左端点,将最大的组合加入堆中。每次取出最大的组合加入答案,并加入以此为左端点的次大组合。可以用 ST 表实现,时间复杂度 \(O((n+k)logn)\)。但是标记每个左端点用过的右端点需要开到 \(O(\frac{n^2}{w})\)(bitset)。
可以将每个组合看作一个三元组 \((p,l,r)\),表示以 \(p\) 为左端点,右端点在 \([l,r]\) 范围内的组合,以此时取到的最大值为关键字排序。若取出 \((p,l,r)\) 加入答案,可以更新为 \((p,l,q-1),(p,q+1,r)\)(\(q\) 为 \([l,r]\) 内最大值的位置)。可以通过。
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
const int N=5e5+100,B=20;
#define gc getchar()
#define rd read()
inline int read(){
int x=0,f=0; char c=gc;
for(;c<'0'||c>'9';c=gc) f|=(c=='-');
for(;c>='0'&&c<='9';c=gc) x=(x<<1)+(x<<3)+(c^48);
return f?-x:x;
}
int n,m,L,R,a[N];
pii mx[N][B+5];
void init(){
for(int i=1;i<=n;++i) mx[i][0]={a[i],i};
for(int k=1;k<=B;++k)
for(int i=1;i+(1<<k)-1<=n;++i)
mx[i][k]=max(mx[i][k-1],mx[i+(1<<(k-1))][k-1]);
}
inline int ask(int l,int r){ int k=__lg(r-l+1); return max(mx[l][k],mx[r+1-(1<<k)][k]).se; }
struct STA{
int p,l,r,q,res;
bool operator<(const STA &x)const{ return res<x.res; }
void init(){ q=ask(l,r),res=a[q]-a[p]; }
}cur,nxt;
priority_queue<STA> q;
signed main(){
n=rd,m=rd,L=rd,R=rd; for(int i=1;i<=n;++i) a[i]=rd+a[i-1];
init();
for(int i=0;i<n;++i) if(L+i<=n) cur={i,L+i,min(n,R+i),0,0},cur.init(),q.push(cur);
int ans=0;
for(int i=1;i<=m;++i){
cur=q.top(),q.pop();
ans+=cur.res;
nxt=cur,nxt.r=cur.q-1; if(nxt.l<=nxt.r) nxt.init(),q.push(nxt);
nxt=cur,nxt.l=cur.q+1; if(nxt.l<=nxt.r) nxt.init(),q.push(nxt);
}
printf("%lld\n", ans);
return 0;
}