题目大意:
题目链接:https://www.luogu.org/problem/P4954
为了调整电灯亮度,贝西要用干草包堆出一座塔,然后爬到牛棚顶去把灯泡换掉。干草包会从传送带上运来,共会出现N包干草,第i包干草的宽度是W i ,高度和长度统一为1。干草塔要从底层开始铺建。贝西会选择最先送来的若干包干草,堆在地上作为第一层,然后再把紧接着送来的几包干草包放在第二层, 再铺建第三层……重复这个过程, 一直到所有的干 草全部用完。每层的干草包必须紧靠在一起,不出现缝隙,而且为了建筑稳定,上层干草的宽度不能超过下层的宽度。 按顺序运来的干草包一定要都用上, 不能将其中几个干草包弃置不用。贝西的目标是建一座最高的塔,请你来帮助她完成这个任务吧。
思路:
把原数列反过来,其实就是要求每次选择的区间必须大于上次选择的区间和。
但是贪心选取是错误的。例如洛谷题解中的这个例子:
9 8 2 1 5 5 9\ 8\ 2\ 1\ 5\ 5 9 8 2 1 5 5
贪心选法: 5 ∣ 5 ∣ 1 2 8 9 5|5|1\ 2\ 8\ 9 5∣5∣1 2 8 9
正确选法: 5 ∣ 5 1 2 ∣ 8 ∣ 9 5|5\ 1\ 2|8|9 5∣5 1 2∣8∣9
那么可以考虑用
d
p
dp
dp解决。
设
f
[
i
]
f[i]
f[i]表示前
i
i
i个干草堆可以堆成的最大高度,
l
e
n
[
i
]
len[i]
len[i]表示在堆成最大高度的前提下,最上面一层的最小宽度,
s
u
m
sum
sum是干草堆宽度的前缀和。
那么有方程
f
[
i
]
=
m
a
x
(
f
[
j
]
)
+
1
∣
s
u
m
[
i
]
−
s
u
m
[
j
]
≥
l
e
n
[
j
]
f[i]=max(f[j])+1\ \ \ \ |\ sum[i]-sum[j]\geq len[j]
f[i]=max(f[j])+1 ∣ sum[i]−sum[j]≥len[j]
由于
f
f
f肯定是单调不减的,所以上述方程的
j
j
j尽量大会更优。
如何取到满足
s
u
m
[
i
]
−
s
u
m
[
j
]
≥
l
e
n
[
j
]
sum[i]-sum[j]\geq len[j]
sum[i]−sum[j]≥len[j]的尽量大的
j
j
j呢?
变形一下成为
s
u
m
[
i
]
≥
s
u
m
[
j
]
+
l
e
n
[
j
]
sum[i]\geq sum[j]+len[j]
sum[i]≥sum[j]+len[j]。
所以我们可以维护一个单调队列存
s
u
m
[
j
]
+
l
e
n
[
j
]
sum[j]+len[j]
sum[j]+len[j],在转移的时候不断弹出前面的满足
s
u
m
[
i
]
≥
s
u
m
[
j
]
+
l
e
n
[
j
]
sum[i]\geq sum[j]+len[j]
sum[i]≥sum[j]+len[j]的元素,只保留最后一个满足
s
u
m
[
i
]
≥
s
u
m
[
j
]
+
l
e
n
[
j
]
sum[i]\geq sum[j]+len[j]
sum[i]≥sum[j]+len[j]的元素进行转移。因为这个元素前面的转移显然没有在这个元素开始转移更优,而后面的就不满足转移的前提。
时间复杂度
O
(
n
)
O(n)
O(n)
代码:
#include <queue>
#include <cstdio>
using namespace std;
const int N=100010;
int n,last,a[N],f[N],sum[N],len[N];
deque<int> q;
int main()
{
scanf("%d",&n);
for (int i=n;i>=1;i--)
scanf("%d",&a[i]);
for (int i=1;i<=n;i++)
sum[i]=sum[i-1]+a[i];
q.push_back(0);
for (int i=1;i<=n;i++)
{
last=-1;
while (q.size() && len[q.front()]+sum[q.front()]<=sum[i])
last=q.front(),q.pop_front();
if (last!=-1) q.push_front(last); //只保留一个满足转移条件的元素来转移
f[i]=f[q.front()]+1;
len[i]=sum[i]-sum[q.front()];
while (q.size() && len[i]+sum[i]<len[q.back()]+sum[q.back()])
q.pop_back(); //维护单调性
q.push_back(i);
}
printf("%d",f[n]);
return 0;
}