题意搞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方程:
设
表示用前i包干草,且当前层用的是第j包到第i包所能达到的最大高度。
则
我们发现这个方程的时间复杂度为
,空间复杂度为
,实在是太太太大了。
我们尝试优化这个方程,但是我们发现无法优化时空都到 的级别。这时候我们从结论入手,设列一个新的dp方程,且可以优化到 级别。
结合贪心的思路尝试倒推:
设
为用i~n来构成的干草堆,底层最短是多少
表示状态f[i]的最大高度
则
由于sum[i]具有单调性,所以我们从单调性开始着手使用单调队列优化d的套路:
在转移方程中可以得到:
从较小的
转移过来会更优秀,所以符合条件的
越小越好 —(1)
然后在判断式中可以看到:
移项,把关于
,
的分别移到两边
那么我们又可以得到:
对于状态i的当前决策
,
越大 ,可以作为决策的情况就越多,这样的j越有用。—(2)
所以我们有了两个分别关于下标和式子的单调条件(1)和(2)
所以我们设两个决策
,
满足
,且
的话,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;
}