正睿noip10连 DAY9 T3 题解(st表 + 技巧)

题目描述

        给定 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 1lrn r − l + 1 ≥ k r - l + 1 \geq k rl+1k,使得 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 1n105,0kmin(100,n),109ai109

分析:

        发现 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=ltp1ai+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} prertprep+sufltsufp 最大。可以发现不管 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 kt 1 1 1。且 k − t k - t kt 随着 t t t 的增大而减小。我们维护一个链表。 l i , r i l_i,r_i liri 分别表示 i i i 位置左右第一个 1 1 1 的位置。那么 t t t 的增大可以看做是左端点 l t lt lt 向左跳一步, k − t k - t kt 的减小可以看做是右端点 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,rrt1] 的区间最大值。设 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
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值