[NOI 2010]超级钢琴 题解

[NOI 2010] 超级钢琴

洛咕传送门

思路

这道题我的思路离正解就差了一步 QAQ.

首先打个暴力上去,可以发现几乎全都 MLE 了。

数据中有一组特殊情况:\(k = 1\) ,即求出最大的区间长度在 \([L,R]\) 间的区间和。

求区间和,不难想到利用前缀和优化,记原数组为 \(a\),前缀和数组 \(sum\) 为:

\[sum_i = \sum_{j=1}^i a_j,i \in [1,n] \]

那么区间 \((l , r]\) 的和可以表示为 \(sum_r - sum_{l}\).

因此,我们枚举右端点 \(r\),取 \([r - R,r - L]\) 区间中的最小 \(sum\) 值即可。

发现可以使用 \(\text{RMQ}\) 优化,时间复杂度 \(O(N\log N)\) ,加上暴力可得 30pts.

code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 5e5 + 5;
int n,k,L,R;
typedef long long ll;
ll a[maxn],sum[maxn],f[maxn][20];
priority_queue<ll> q;
void ST() {
	for(int k = 1;(1 << k) < n;++ k) {
		for(int i = 0;i + (1 << k) - 1 < n;++ i) {
			f[i][k] = min(f[i][k - 1] , f[i + (1 << (k - 1))][k - 1]);
		}
	}
	return ;
}
int lg[maxn];
int RMQ(int l,int r) {
	int k = lg[r - l + 1];
	return min(f[l][k] , f[r - (1 << k) + 1][k]);
}
void work() {
	for(int i = 0;i < n;++ i)f[i][0] = sum[i];
	for(int i = 2;i <= n;++ i)lg[i] = lg[i >> 1] + 1;
	ST();
	ll ans = sum[L];
	for(int i = L;i <= R;++ i) {
		ans = max(ans , sum[i] - RMQ(i - L , max(i - R , 0)));
	}
	printf("%lld",ans);
	return ;
} 
int main() {
	scanf("%d%d%d%d",&n,&k,&L,&R);
	for(int i = 1;i <= n;++ i)scanf("%lld",&a[i]),sum[i] = sum[i - 1] + a[i];
	if(k == 1) {
		work();
		return 0;
	}
	for(int i = 1;i <= n - L + 1;++ i) {
		for(int j = i + L - 1;j <= min(n , i + R - 1);++ j) {
			ll val = sum[j] - sum[i - 1];
			q.push(val);
		}
	}
	ll ans = 0;
	for(int i = 1;i <= k;++ i) {
		ans += q.top();
		q.pop();
	}
	printf("%lld",ans);
	return 0;
}

再想一想,可不可以再优化一下呢?

取前 \(k\) 大的区间和,显然是采用贪心的思路。

结合特殊样例,出题人似乎是在暗示从 \(k=1\) 的情况推广,进行尝试。

\(k = 1\) 时,直接取最大区间和即可。

那么当 \(k = 2\) 时,我们该怎么办呢?如何求出次大的值呢?

然后我就卡在这里了

当最大的区间 \([t , r_0]\) 取到后,对于所有的 \(r \neq r_0\)\(r\) 为区间末尾时区间的最大区间和显然可以作为备选方案。

但还有一种情况:\(r_0\) 为区间末尾时区间的次大区间和也可以作为备选方案。

\(r_0\) 的对应合法区间为 \([l,r]\) ,那么 \([l,t-1]\)\([t + 1,r]\) 中也可能会产生一个全局次大和。

这种情况下显然可以直接枚举一下,对比两种情况的较大者即可。

推广一下,不难想到正解:

  • 建立一个以区间和为关键字的大根堆 \(q\)\(q\) 中结构体元素分别为可取的区间边界、\(r\)、对应的 \(t\)

  • 循环 \(k\) 次,每次取出堆顶 \(u\),设对应的区间尾端为 \(r_0\) ,边界为 \([l,r]\) ,答案累加上 \(sum_{r_0}-sum_t\) ,再把\((l,t-1,r_0,\text{RMQ(l,t-1)})\),\((t+1,r,r_0,\text{RMQ(t+1,r)})\) 加入堆,最后输出答案即可

code:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <cmath>
using namespace std;
const int maxn = 5e5 + 5;
int n,k,L,R;
typedef long long ll;
ll a[maxn],sum[maxn],f[maxn][20];
int pos[maxn][20];
struct Chtholly {
	int x,l,r,t;
	Chtholly() {
		x = l = r = t = 0;
	}
	Chtholly(int x,int l,int r,int t):x(x),l(l),r(r),t(t){};
	bool operator < (const Chtholly& p)const {
		return sum[x] - sum[t] < sum[p.x] - sum[p.t];
	}
};
priority_queue<Chtholly> q;
void ST() {
	for(int k = 1;(1 << k) <= n + 1;++ k) {
		for(int i = 0;i + (1 << k) - 1 <= n;++ i) {
			if(f[i][k - 1] < f[i + (1 << (k - 1))][k - 1]) {
				f[i][k] = f[i][k - 1];
				pos[i][k] = pos[i][k - 1];
			}
			else {
				f[i][k] = f[i + (1 << (k - 1))][k - 1];
				pos[i][k] = pos[i + (1 << (k - 1))][k - 1];
			}
		}
	}
	return ;
}
int lg[maxn];
int RMQ(int l,int r) {
	if(l > r)swap(l , r);
	int k = lg[r - l + 1];
	if(f[l][k] < f[r - (1 << k) + 1][k])return pos[l][k];
	return pos[r - (1 << k) + 1][k];
}
void init_RMQ() {
	for(int i = 0;i <= n;++ i)f[i][0] = sum[i],pos[i][0] = i;
	for(int i = 2;i <= n;++ i)lg[i] = lg[i >> 1] + 1;
	ST();
	for(int i = L;i <= n;++ i) {
		int l = i - L,r = max(i - R , 0);
		int t = RMQ(r , l);
		q.push(Chtholly(i , r , l , t));
	}
	return ;
} 
int main() {
	scanf("%d%d%d%d",&n,&k,&L,&R);
	for(int i = 1;i <= n;++ i)scanf("%lld",&a[i]),sum[i] = sum[i - 1] + a[i];
	init_RMQ();
	ll ans = 0;
	for(int i = 1;i <= k;++ i) {
		Chtholly u = q.top();
		q.pop();
		int l = u.l,r = u.r,t = u.t,x = u.x;
		ans += sum[x] - sum[t];
		if(l < t)q.push(Chtholly(x , l , t - 1 , RMQ(l , t - 1)));
		if(r > t)q.push(Chtholly(x , t + 1 , r , RMQ(t + 1 , r)));
	}
	printf("%lld",ans);
	return 0;
}
posted @ 2022-01-18 22:40  ImALAS  阅读(53)  评论(1编辑  收藏  举报