题意搞skr人…,其实就是堆方块:
有n(n<=100000)个干草,每堆有个宽度,现在要且分成若干段,把每一段的干草按顺序堆起来形成一个多层的干草堆(所以下标越小的干草堆放在越下面)且宽度要逐层非严格递减(上面一层的宽度<=下面一层的宽度),求最多可以放多少层。
好神啊这题。。
题意看起来不复杂,所以我们很容易想到一个贪心:
从上往下倒着考虑,假设我们已经知道上面一层的宽度为w,那么下面一层的最优的策略一定是的大于等于w的宽度和中最靠近w的。
想出贪心之后我们需要证明他的正确性,但是也可以证明贪心是错的:
在bzoj该题的Discuss下id为thy的大佬已经举出了一个例子证明贪心是错的:
ex:
16
6 1 1 1 6 1 1 1 1 6 1 1 1 1 1 6
贪心:
6
111116
111161116
最优:
6111
1161
1116
1116
为什么是错的呢?
因为第i层可以帮第i+1层分担一些宽度,使得i+2层压力更小
但是我们可以在证明贪心是错误的过程中模糊地猜到一个结论:
假如该问题的一个解是最优解,那么它的最底层宽度一定最小。
这个结论有没有用我们现在还不知道,但是zory左老师有言:
每一个题目肯定有每一个题目的特质,肯定要抓住这个特质来解题。
为啥,因为最底层最小可以让干草堆叠的尽量的高嘛…
然后我们往dp去想,设列一个初级dp方程:
设
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示用前i包干草,且当前层用的是第j包到第i包所能达到的最大高度。
则
f
[
i
]
[
j
]
=
m
a
x
{
f
[
j
−
1
]
[
k
]
}
+
1
(
s
u
m
[
i
]
−
s
u
m
[
j
−
1
]
≤
s
u
m
[
j
−
1
]
−
s
u
m
[
k
−
1
]
,
k
<
j
≤
i
)
f[i][j]=max\lbrace f[j-1][k]\rbrace+1(sum[i]-sum[j-1] \leq sum[j-1]-sum[k-1],k<j\leq i)
f[i][j]=max{f[j−1][k]}+1(sum[i]−sum[j−1]≤sum[j−1]−sum[k−1],k<j≤i)
我们发现这个方程的时间复杂度为
O
(
N
3
)
O(N^3)
O(N3),空间复杂度为
O
(
N
2
)
O(N^2)
O(N2),实在是太太太大了。
我们尝试优化这个方程,但是我们发现无法优化时空都到 O ( N ) O(N) O(N)的级别。这时候我们从结论入手,设列一个新的dp方程,且可以优化到 O ( N ) O(N) O(N)级别。
结合贪心的思路尝试倒推:
设
f
[
i
]
f[i]
f[i]为用i~n来构成的干草堆,底层最短是多少
g
[
i
]
g[i]
g[i]表示状态f[i]的最大高度
则
f
[
i
]
=
m
i
n
{
s
u
m
[
j
−
1
]
−
s
u
m
[
i
−
1
]
}
(
i
<
j
≤
n
,
f
[
j
]
≤
s
u
m
[
j
−
1
]
−
s
u
m
[
i
−
1
]
)
f[i]=min\lbrace sum[j-1]-sum[i-1]\rbrace(i<j\leq n,\ \ \ f[j] \leq sum[j-1]-sum[i-1])
f[i]=min{sum[j−1]−sum[i−1]}(i<j≤n, f[j]≤sum[j−1]−sum[i−1])
g
[
i
]
=
g
[
j
]
+
1
g[i]=g[j]+1
g[i]=g[j]+1
由于sum[i]具有单调性,所以我们从单调性开始着手使用单调队列优化d的套路:
在转移方程中可以得到:
f
[
i
]
f[i]
f[i]从较小的
j
j
j转移过来会更优秀,所以符合条件的
j
j
j越小越好 —(1)
然后在判断式中可以看到:
f
[
j
]
≤
s
u
m
[
j
−
1
]
−
s
u
m
[
i
−
1
]
f[j] \leq sum[j-1]-sum[i-1]
f[j]≤sum[j−1]−sum[i−1]
移项,把关于
i
i
i,
j
j
j的分别移到两边
s
u
m
[
i
−
1
]
≤
s
u
m
[
j
−
1
]
−
f
[
j
]
sum[i-1] \leq sum[j-1]-f[j]
sum[i−1]≤sum[j−1]−f[j]
那么我们又可以得到:
对于状态i的当前决策
j
j
j,
s
u
m
[
j
−
1
]
−
f
[
j
]
sum[j−1]−f[j]
sum[j−1]−f[j]越大 ,可以作为决策的情况就越多,这样的j越有用。—(2)
所以我们有了两个分别关于下标和式子的单调条件(1)和(2)
所以我们设两个决策
j
j
j,
k
k
k满足
k
>
j
k>j
k>j,且
s
u
m
[
k
−
1
]
−
f
[
k
]
≤
s
u
m
[
j
−
1
]
−
j
f
[
j
]
sum[k-1]-f[k] \leq sum[j-1]-jf[j]
sum[k−1]−f[k]≤sum[j−1]−jf[j]的话,k就是无用状态可以删去。
用单调队列维护即可,注意由于我们是倒推所以单调队列的head和tail要反过来(因为我们默认head<=tail)
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
typedef long long ll;
const int N=1e5+10;
int a[N],sum[N];
int list[N],head,tail;
int f[N],g[N];
int main()
{
int n;scanf("%d",&n);
sum[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
list[1]=n+1; head=tail=1; //注意由于倒序所以head和tail反过来
for(int i=n;i>=0;i--)
{
while(head<tail && f[list[head+1]]<=sum[list[head+1]-1]-sum[i-1]) head++;
int j=list[head];
f[i]=sum[j-1]-sum[i-1];
g[i]=g[j]+1;
while(head<=tail && sum[i-1]-f[i]>=sum[list[tail]-1]-f[list[tail]]) tail--;
list[++tail]=i;
}
printf("%d\n",g[1]);
return 0;
}