https://www.luogu.org/problem/P2048
题意
给一个序列A,输出前k大的子区间和 的和
n , k <= 500000
-1000 ≤ Ai ≤ 1000,1 ≤ L ≤ R ≤ n
分析
在读清题之后很容易想到暴力,用前缀和来算区间和 ,把区间和加入堆中,取出前k大的相加即可
但是这样会MLE, 超内存
再次看题,想想自己的暴力,我们知道以第o个音符为左端点的, 合法的子区间有很多, 所以会MLE,但是题目求的是最大的几个区间和,所以我们得到一个贪心策略:每次加入堆的是以o为左端点的最优, 也就是最大的区间和,而不是全加进去,这样就不会MLE了。
定义三元组(o, l, r) = max(sum[t] - sum[o-1] | t∈[l, r] ) , (o, l, r)表示以o为左端点, 右端点在[l, r]范围内的区间和最大值, 因为sum[o-1]是不变的, 所以我们只需要sum[t]最大即可,静态最大——RMQ。
但是,我们再想一想贪心的策略, 错啦。 虽然它不能再选最优位置 t了,但是可能还有次优的,前k个大的,区间和:(o, l, t-1)和(o, t+1, r),为了避免重复且不丧失其他较优解(即保证解正确完整),我们仍然要把 (o, l, t - 1)(o, l, t−1) , 扔回堆里面去。显然地,在放回去之前应该保证区间的存在,即 l = t 或 r = t 的情况要进行特判。
这里把 (o, l, t - 1),(o, t+1, r) 扔会堆里的操作显然需要我们维护位置, 而st表中保存的是值,所以需要动一下脑子, 把st里维护的东西改为位置。此操作是在取出(o, l, r) 之后做的,因为它们没有(o, l, r)优, 而且这样在维护位置之后很方便
#include<cstdio>
#include<queue>
using namespace std;
#define MAX 500000
#define ll long long
int sum[MAX], f[MAX][19];
int n,k, l, r;
void st() {
for(int k = 1; (1<<k) <= n; k++)
for(int i = 1; i+(1<<k)-1 <= n; i++) {
int x = f[i][k-1] , y = f[i + (1<<(k-1))][k-1];
f[i][k] = sum[x] > sum[y] ? x : y;
}
}
int RMQ(int l, int r) {
int k = 0;
while(1<<(k+1) <= r-l+1) k++;
int x = f[l][k], y = f[r-(1<<k)+1][k];
return sum[x] > sum[y] ? x : y;
}
struct node{
int o, l, r, t;
node(int o = 0, int l = 0, int r = 0) : o(o), l(l), r(r), t(RMQ(l, r)) {}
bool operator < (const node& xxx) const {
return sum[t]-sum[o-1] < sum[xxx.t]-sum[xxx.o-1];
}
};
priority_queue <node> q;
int main() {
scanf("%d%d%d%d", &n,&k,&l,&r);
for(int i = 1; i <= n; i++) {
scanf("%d",&sum[i]);
f[i][0] = i;
sum[i] += sum[i-1];
}
st();
for(int i = 1; i <= n; i++)
if(i+l-1 <= n) q.push(node(i, i+l-1, min(i+r-1, n) )) ;
ll ans = 0;
int o, l, r, t;
for(int i = 1; i <= k; i++) {
o = q.top().o, l = q.top().l, r = q.top().r, t = q.top().t;
q.pop() ;
ans += (ll)(sum[t]-sum[o-1]);
if (l != t) q.push(node(o, l, t - 1));
if (t != r) q.push(node(o, t + 1, r));
}
printf("%lld", ans);
}