题目描述
给定
n
,
k
n, k
n,k 和序列
a
1
,
a
2
,
.
.
.
,
a
n
a_1, a_2, ..., a_n
a1,a2,...,an, 序列中的元素可正可负。
你需要找到一个区间
[
l
,
r
]
[l, r]
[l,r] 满足
1
≤
l
≤
r
≤
n
1 \leq l \leq r \leq n
1≤l≤r≤n 且
r
−
l
+
1
≥
k
r - l + 1 \geq k
r−l+1≥k,使得
a
l
+
a
l
+
1
+
.
.
+
a
r
a_l + a_{l + 1} + .. + a_r
al+al+1+..+ar 减去区间
[
l
,
r
]
[l, r]
[l,r] 中最大的
k
k
k 个数后得到的答案最大。
1
≤
n
≤
1
0
5
,
0
≤
k
≤
m
i
n
(
100
,
n
)
,
−
1
0
9
≤
a
i
≤
1
0
9
1 \leq n \leq 10^5,0 \leq k \leq min(100, n),-10^9 \leq a_i \leq 10^9
1≤n≤105,0≤k≤min(100,n),−109≤ai≤109。
分析:
发现 k k k 的取值比较小,不难想到这道题要从 k k k 入手。
我们先来考虑 k = 0 k = 0 k=0 该怎么做:那么显然,这个问题等价于找到一段长度至少为 1 1 1 的区间,使得区间和最大。用 前缀和 可以在 O ( n ) O(n) O(n) 的复杂度完成。
接下来我们考虑 k = 1 k = 1 k=1 该怎么做:
问题变成了需要找到一段长度至少为 1 1 1 的区间,使得 区间和 减去 区间最大值 最大。我们可先通过 单调栈 维护一个数作为最大值能够覆盖到的左右区间,然后枚举每一个位置 p p p,钦定它为最大值。设它作为最大值能覆盖的区间是 [ L , R ] [L,R] [L,R],那么我们需要找到两个位置 l t lt lt 和 r t rt rt,使得 ∑ i = l t p − 1 a i + ∑ j = p + 1 r t a j \sum_{i = lt}^{p - 1}a_i + \sum_{j = p + 1}^{rt}a_j ∑i=ltp−1ai+∑j=p+1rtaj 最大。
我们可以处理出来每一个位置的 前缀和 和 后缀和。那么也就变成了求 p r e r t − p r e p + s u f l t − s u f p pre_{rt} - pre_{p} + suf_{lt} - suf_{p} prert−prep+suflt−sufp 最大。可以发现不管 l t lt lt , r t rt rt 是什么值, p r e p pre_p prep 和 s u f p suf_p sufp 都不会改变,我们只需要分别求出最大的 p r e r t pre_{rt} prert 和 s u f l t suf_{lt} suflt 就好了。这个可以建两个 st表 然后快速得到。
最后,我们考虑正解:
首先是一个很典型的思路:我们可以枚举第 k k k 大的数字在哪个位置上。设这个数字大小是 x x x,那么把大于 x x x 的设为 1 1 1,小于 x x x 的设成 0 0 0。那么对于我们枚举的位置,我们只需要找到一个 经过这个位置,包含 k k k 个 1 1 1,且其它是 0 0 0 的位置上的数之和最大的区间。
我们首先对于每一个数 设定一个大小关系:如果数本身大小不同,那么较大的数更大;如果大小相同,那么靠右边的数更大。这样做的好处是我们可以不用再去考虑 和当前枚举的位置上的数字相同的数字 该设成 0 0 0 还是 1 1 1 了。在这样的顺序下,每个位置都有了一个严格的大小关系。
然后我们 从小到大 枚举第 k k k 大。开始时整个数组都是 1 1 1。因为 k k k 的规模比较小,我们枚举这个位置左边有 t t t 个 1 1 1,那么很显然这个位置右边(包含这个位置)需要有 k − t k - t k−t 个 1 1 1。且 k − t k - t k−t 随着 t t t 的增大而减小。我们维护一个链表。 l i , r i l_i,r_i li,ri 分别表示 i i i 位置左右第一个 1 1 1 的位置。那么 t t t 的增大可以看做是左端点 l t lt lt 向左跳一步, k − t k - t k−t 的减小可以看做是右端点 r t rt rt 向左跳一步。然后用 st表 查询 [ l l t + 1 , l t ] [l_{lt} + 1, lt] [llt+1,lt] 的 s u f suf suf 最大值 和 [ r t , r r t − 1 ] [rt, r_{rt} - 1] [rt,rrt−1] 的区间最大值。设 x x x 是枚举的位置,那么再减去 p r e x pre_{x} prex, b a c x bac_{x} bacx 和除了 x x x 区间内其它 1 1 1 对应的数字大小就好了。
时间复杂度可以做到 O ( n k ) O(nk) O(nk)。 细节不少。
CODE:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
inline int read(){
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){if(c == '-') f = -1; c = getchar();}
while(isdigit(c)){x = (x << 1) + (x << 3) + (c ^ 48); c = getchar();}
return x * f;
}
namespace BT{
LL c[N];
int lowbit(int x){return x & -x;}
void add(int x, LL y){for(; x < N; x += lowbit(x)) c[x] += y;}
LL ask(int x){
LL res = 0;
for(; x; x -= lowbit(x)) res += c[x];
return res;
}
}
int n, l[N], r[N], id[N], k;//指针
LL a[N], pre[N], suf[N], Maxn[2][25][N], res = -1e18, sum[N];// 0 -> pre 1 -> suf
LL query(int t, int l, int r){
int k = log2(r - l + 1);
return max(Maxn[t][k][l], Maxn[t][k][r - (1 << k) + 1]);
}
void build_ST(){
for(int i = 1; i <= n; i++){
Maxn[0][0][i] = pre[i];
Maxn[1][0][i] = suf[i];
}
for(int i = 1; i <= 24; i++){
for(int j = 1; j + (1 << i) - 1 <= n; j++){
Maxn[0][i][j] = max(Maxn[0][i - 1][j], Maxn[0][i - 1][j + (1 << (i - 1))]);
Maxn[1][i][j] = max(Maxn[1][i - 1][j], Maxn[1][i - 1][j + (1 << (i - 1))]);
}
}
}
bool cmp(int x, int y){return ((a[x] < a[y]) || (a[x] == a[y] && x < y));}
void solve(){
LL ans = -1e18, del = 1e18;
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
for(int i = 1; i <= n; i++){
del = min(del, sum[i - 1]);
ans = max(ans, sum[i] - del);
}
printf("%lld\n", ans);
}
int main(){
n = read(); k = read();
for(int i = 1; i <= n; i++){
a[i] = 1LL * read();
}
if(k == 0) solve();
else{
for(int i = 1; i <= n; i++){
l[i] = i - 1; r[i] = i + 1;
id[i] = i; pre[i] = pre[i - 1] + a[i];
}
for(int i = n; i >= 1; i--)
suf[i] = suf[i + 1] + a[i];
build_ST();
sort(id + 1, id + n + 1, cmp);//从小到大依次枚举第 k 大的数字, 枚举第 k + 1 大可能会有点难写
for(int i = 1; i <= n; i++){
int p = id[i];// p是第k大, 我们钦定大小大小相同的数也有一个相对大小
int cnt = 1, rt = p;
LL sum = 0;
while(cnt < k && r[rt] != n + 1){
cnt++; rt = r[rt];
sum += a[rt];
}
int lt = p;
while(cnt < k){
if(l[p] == 0) break;
lt = l[lt], cnt++;
sum += a[lt];
}
if(cnt == k){//够
while(lt != 0 && rt >= p){
res = max(res, query(0, rt, r[rt] - 1) + query(1, l[lt] + 1, lt) - pre[p] - suf[p] - sum);
sum -= a[rt];
rt = l[rt];
lt = l[lt];
sum += a[lt];
}
}
r[l[p]] = r[p];
l[r[p]] = l[p];
}
printf("%lld\n", res);
}
return 0;
}
/*
10 1
2 5 2 3 -1 0 12 6 2 -4
*/