[题解]P3503 [POI 2010] KLO-Blocks

P3503 [POI 2010] KLO-Blocks

一个区间能做到高度全部 \(\ge k\),当且仅当该区间的平均值 \(\ge k\)

于是我们为每个 \(a_i\) 减去 \(k\),再求出其前缀和数组 \(s\)

问题转化为求:

\[\large \max\limits_{l\le r,s_r-s_{l-1}\ge 0} (r-l+1) \]

暴力可以做到 \(O(n^2)\) 的时间复杂度,考虑优化。

我们发现,若 \(i<j\),且 \(s_i\le s_j\),那么 \(i+1\) 作左端点一定不劣于 \(j+1\) 作左端点;同理,此时 \(j\) 作右端点一定不劣于 \(i\) 作右端点。

换句话说,我们可以规定,只有前缀最小值处才能作为 \(l-1\),只有后缀最大值处才能作为 \(r\)

我们可以用两个单调栈来维护这些左右端点。

根据上面的分析,可知在下标升序的情况下,所有可能的左右端点均是按 \(s\) 单减的。

另外一种方法是仅存储左端点,然后从后往前枚举右端点,显然前一种方法中参与统计的答案是此做法的子集,所以仍然是正确的。

时间复杂度均为 \(O(nm)\)

写法 $1$ - R234517631
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,a[N],s[N],sl[N],sr[N],tl,tr;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	while(m--){
		int k,ans=tl=tr=0;
		cin>>k;
		for(int i=1;i<=n;i++) s[i]=s[i-1]+a[i]-k;
		sl[++tl]=0;//l-1=0 也要算入
		for(int i=1;i<=n;i++) if(s[i]<s[sl[tl]]) sl[++tl]=i;
		for(int i=n;i;i--) if(!tr||s[i]>s[sr[tr]]) sr[++tr]=i;
		for(int i=1,j=tr;i<=tl;i++){
			for(;j&&s[sl[i]]<=s[sr[j]];j--) ans=max(ans,sr[j]-sl[i]);
		}
		cout<<ans<<" ";
	}
	return 0;
}
写法 $2$ - R234266797
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
int n,m,a[N],s[N],st[N],top;//st 存储 l-1
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	int k,ans;
	while(m--){
		cin>>k;
		ans=0;
		st[top=1]=0;//l-1=0 也要算入
		for(int i=1;i<=n;i++){
			s[i]=s[i-1]+a[i]-k;
			if(s[i]<s[st[top]]) st[++top]=i;
		}
		for(int i=n;i;i--){
			while(top&&s[st[top]]<=s[i]) ans=max(ans,i-st[top--]);
		}
		cout<<ans<<" ";
	}
	return 0;
}
posted @ 2025-09-06 19:33  Sinktank  阅读(70)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.