木桶效应题目技巧
以下面这道题开始展开
题目描述:
假设有 n 块木板,这 n 块木板排成一排,每块木板长度为 ai。
当使用连续的一段木板作木桶时,木桶的容量为这些木板长度的最小值。
对于任意一个 x(1≤x≤n),你需要回答如果使用 n 块木板中的连续 x 块木板做木桶,木桶的最大容量分别为多少?
输入描述
第一行为一个数 n,表示木板数量。
第二行有 n 个整数 a1,a2,a3,a4 …an,分别表示每块木板长度。
输出描述
输出n个数,分别表示x为1,2,3,。。。n时木桶的最大容量。
案例
输入
10
9 2 7 3 5 6 4 8 7 1
输出
9 7 4 4 4 3 3 2 2 1
数据范围:
对于所有数据满足:1≤n≤10^6, 1≤ai≤10^9。
--------------------------------------------------分割线---------------------------------------------------------
很明显,该题的数据范围很大,如果暴力枚举不同区间长度的串来确定区间最小值,那很明显会超时,利用动态规划来求得所有情况也无疑会超时。
这里就需要注意到,题目中的某个区间的木桶容量只与该区间的最小值有关,以区间下标为i的位置为例,如果这个值为某个区间的最小值,则应该保证从该区间开始,从左或从右,两边的值都应该比这个值大,那么它也就可以作为区间长度为右边第一个比它小的数所在的下标和左边第一个比它小的数所在的下标的差-1这样长度的一个区间的最小值,每次都处理出这样的最小值然后迭代刷新这个值,最后就可以处理出长度已知的一些区间的桶的最大容量,而这样显然有时候还会有有的区间并不能获得桶的最大容量,这里我们可以想到,当桶所占区间变长时,这个桶容量毫无疑问是一个不严格单调递减的变量,我们可以往已经求得的区间的最大桶容量上靠,这样也就可以得到其他没有被求出结果的区间的桶的最大容量了,因为我们可以知道这个已知的稍大一点的区间是在全局上能取到的最大容量,往它靠时也就是未赋值时能取到的最大容量。
为此我们这里考虑单调队列或者单调栈,以o(n)的复杂度求出距离比a[i]小的离第i个位置最近的值的那两个个位置,也就是从左到右和从右到左分别来一次单调队列(栈),求出l[i],r[i],最后迭代刷新容量值即可。
Code
#include <bits/stdc++.h>
#define N 1000010
#define MOD 1000000007
#define LL long long
using namespace std;
#define rep(i,a,b) for(LL i=a;i<=b;i++)
int n, r[N], l[N], a[N], c[N], ans[N];
stack<int> s;
int main() {
#ifndef ONLINE_JUDGE
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; ++i) {
r[i] = n + 1;
}
for (int i = 1; i <= n;) {
while (!s.empty() && a[i] < a[s.top()]) {
r[s.top()] = i;
s.pop();
}
s.push(i++);
}
while (!s.empty()) s.pop();
for (int i = n; i >= 1;) {
while (!s.empty() && a[i] < a[s.top()]) {
l[s.top()] = i;
s.pop();
}
s.push(i--);
}
//l,r数组分别为以i为开始,向左向右走第一个小于a[i]值的位置的下标
for (int i = 1; i <= n; ++i) {
c[r[i] - l[i] - 1] = max(a[i], c[r[i] - l[i] - 1]);
}
for (int i = n; i >= 1; --i) {
if (c[i] < c[i + 1])
c[i] = c[i + 1];
}//倒序,o(n)便可赋完未获取区间的最大容量
for (int i = 1; i <= n; ++i) {
printf("%d ", c[i]);
}
return 0;
}