洛谷 P3503 [POI 2010] KLO-Blocks 题解
题目大意
洛谷 P3503 [POI 2010] KLO-Blocks
给定一个序列 \(a_{[1..n]}\),求经过操作后最长的满足每个数都大于等于 \(k\) 的连续子序列的长度。
操作内容:对于一个大于 \(k\) 的数,可以使它自身减 \(1\) 的同时让它前一个或后一个数加 \(1\)
思路分析
首先对于一个序列,若 \(k=0\),则一定可以使它的每个元素相等且大于等于原序列元素和的平均数的整数部分。
但是,现在 \(k\ne 0\)。注意到最终每个元素都要跟 \(k\) 比较,那我们可不可以将每个数都减去 \(k\),最终跟 \(0\) 比较呢?答案是可行的。
同时对改动后的数组求个前缀和 \(b_{[1..n]}\),则只需要满足 \(b_j-b_{i-1}>0\),\(a_{[i..j]}\) 即满足条件。对于给定的右端点 \(j\),由于需要最大化连续子序列长度,我们应找到最小的 \(i\) 满足 \(b_{i-1}<b_j\)。所以我们可以维护一个单调栈来存储可以作为左端点的 \(i\),满足对于元素 \(x\) 和 \(y\),若 \(x\) 比 \(y\) 更靠近栈顶,则应满足 \(x>y\) 且 \(b_x<b_y\)。所以我们可以正序遍历数组,维护栈。
而题目中未确定右端点 \(j\),所以我们从右到左枚举右端点,若栈顶元素可作为左端点,则由于更小的右端点即使可以与栈顶匹配成序列,则长度也必定更短,所以可以弹出栈顶,更新答案。
二分 hack 数据
input:
8 1
9 4 10 3 9 6 7 9
7
output:
8
这是由于无论选前 \(7\) 个还是后 \(7\) 个都是 \(9+4+10+3+9+6+7=46<7\times 7\)。所以程序不会去判断全选是否可行。
代码呈现
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
int n,m;
int a[N];
ll sa[N];
int main(){
scanf("%d%d",&n,&m);
for (int i=1;i<=n;++i) scanf("%d",a+i);
while (m--){
int k,ans=0;
scanf("%d",&k);
for (int i=1;i<=n;++i) sa[i]=sa[i-1]+a[i]-k;
stack<int> stk; // 单调栈
for (int i=1;i<=n;++i){ // 维护
if (sa[i]>=0) ans=max(ans,i);
if (stk.empty() || sa[i]<sa[stk.top()]) stk.push(i);
}
for (int i=n;i>=1;--i){ // 求解
while (!stk.empty() && sa[i]>=sa[stk.top()])
ans=max(ans,i-stk.top()),stk.pop();
}
printf("%d ",ans);
}
return 0;
}

浙公网安备 33010602011771号