「NOI2010」超级钢琴

6 篇文章 0 订阅
3 篇文章 0 订阅

link

RMQ练手题,只要不把题目读错就是道水题。

因为是连续的,不妨考虑前缀和。注意这里超级和弦不同值它们的位置不同(不是值)。

即求 m a x ( ∑ p r e [ r ] − p r e [ l − 1 ] ) max({\sum pre[r]-pre[l-1]}) max(pre[r]pre[l1])。把所有的数放进优先队列里显然是不行的。

套路,考虑将所有的左端点放进优先队列里面,则为一个五元组 ( i , l , r , v , n u m ) (i,l,r,v,num) (i,l,r,v,num) v v v 表示 m a x ( p r e [ l → r ] − p r e [ i − 1 ] ) max(pre[l\to r]-pre[i-1]) max(pre[lr]pre[i1]) n u m num num 表示取到最大值的下标,想到了什么,RMQ!优先队列里就按 v v v 排序,取出五元组后,断成 ( i , l , n u m − 1 , v 1 , n u m 1 ) (i,l,num-1,v1, num1) (i,l,num1,v1,num1) ( i , n u m + 1 , r , v 2 , n u m 2 ) (i,num+1,r,v2,num2) (i,num+1,r,v2,num2)。想想就会发现这个正确性是可以保证的。因为每次只会增加一个元素,时间复杂度: O ( k l o g 2 ( n ) ) \mathcal{O}(klog_2(n)) O(klog2(n))

Code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <climits>
#include <queue>
#define Max(x, y) ((x)>(y)?(x):(y))
#define Min(x, y) ((x)<(y)?(x):(y))
using namespace std;
const int MAXN = 5e5 + 5;
struct Node {
	int id, l, r, val, num;
	bool operator < (const Node P) const { return val < P.val; }
	Node() {}
	Node(int x, int y, int z, int u, int v) { id = x; l = y; r = z; val = u; num = v; }
};
priority_queue <Node> que;
int n, k, l, r, a[MAXN], pre[MAXN], t, dp[20][MAXN], num[20][MAXN];
long long res;
void GET_RMQ() {
	for(int i = 1; i <= t; i ++) {
		for(int j = 1; j <= (n - (1 << i) + 1); j ++) {
			dp[i][j] = Max(dp[i - 1][j], dp[i - 1][j + (1 << (i - 1))]);
			num[i][j] = dp[i - 1][j] > dp[i - 1][j + (1 << (i - 1))] ? num[i - 1][j] : num[i - 1][j + (1 << (i - 1))];
		}
	}
}
int Query_val(int x, int y) { int p = log(y - x + 1) / log(2); return Max(dp[p][x], dp[p][y - (1 << p) + 1]); }
int Query_num(int x, int y) { int p = log(y - x + 1) / log(2); return dp[p][x] >= dp[p][y - (1 << p) + 1] ? num[p][x] : num[p][y - (1 << p) + 1]; }
int main() {
	scanf("%d%d%d%d", &n, &k, &l, &r); t = log(n) / log(2) + 1;
	for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), pre[i] = pre[i - 1] + a[i], dp[0][i] = pre[i], num[0][i] = i;
	GET_RMQ();
	for(int i = 1; i <= n; i ++) {
		if(i + l - 1 <= n) {
			int L = i + l - 1, R = Min(i + r - 1, n);
			que.push(Node(i, L, R, Query_val(L, R) - pre[i - 1], Query_num(L, R)));
		}
	}
	for(int i = 1; i <= k; i ++) {
		if(que.empty()) break;
		Node t = que.top(); que.pop();
		res += t.val;
		if(t.l <= t.num - 1) que.push(Node(t.id, t.l, t.num - 1, Query_val(t.l, t.num - 1) - pre[t.id - 1], Query_num(t.l, t.num - 1)));
		if(t.num + 1 <= t.r) que.push(Node(t.id, t.num + 1, t.r, Query_val(t.num + 1, t.r) - pre[t.id - 1], Query_num(t.num + 1, t.r)));
	}
	printf("%lld", res);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值