一道单调队列优化(DP)
原题链接
首先想到的应该是(O(n^3))的朴素(DP)。
定义(f[i][j])表示第(jsim i)块干草堆作为顶层时的最大高度。
(qquadqquad f[i][j]=maxlimits_{k=1}^{j-1}{f[j-1][k]}+1,(sum[i]-sum[j-1]leqslant sum[j-1]-sum[k-1]))
但显然时间空间全炸了,这时我们需要知道一个结论,即最优解的最底层在所有解中一定是最小的(下面越窄才能叠的越高)。
任意取出一个能使层数最高的方案,设有CA层,把其中从下往上每一层最大的块编号记为Ai;任取一个能使底边最短的方案,设有CB层,把其中从下往上每一层最大的块编号记为Bi。显然A1>=B1,ACB<=BCB,这说明至少存在一个k属于(1,CB),满足Ak-1>=Bk-1且Ak<=Bk。也就是说,方案 A 第K 层完全被方案 B 第K 层包含。构造一个新方案,第K 层往上按方案 A,往下按方案 B,两边都不要的块放中间当第K 层。新方案的层数与 A 相同,而底边长度与 B 相同。证毕。
证明by zkw
因为正着推并不单调,所以我们要倒着来推。
定义(f[i])表示第(isim n)块干草堆构成的塔的最底层的宽度,(h[i])表示状态(f[i])下的最大高度。
(qquadqquad f[i]=minlimits_{j=i+1}^n{sum[j-1]-sum[i-1]},(f[j]leqslant sum[j-1]-sum[i-1]))
(qquadqquad h[i]=h[j]+1)
时间复杂度(O(n^2))
显然(j)越小越好。
将(f[i])的转移方程条件移项得(sum[i-1]leqslant sum[j-1]-f[j]),所以对于一个决策(k),如果有一个决策(j)满足(k>j ext{且}sum[k-1]-f[k]leqslant sum[j-1]-f[j]),即可认为(k)是无用决策。
这样就可以用单调队列来维护了。
#include<cstdio>
using namespace std;
const int N = 1e5 + 10;
int f[N], h[N], a[N], q[N];
int re()
{
int x = 0;
char c = getchar();
bool p = 0;
for (; c<'0' || c>'9'; c = getchar())
p = (c == '-' || p) ? 1 : 0;
for (; c >= '0'&&c <= '9'; c = getchar())
x = x * 10 + (c - '0');
return p ? -x : x;
}
int main()
{
int i, l = 1, r = 1, n;
n = re();
for (i = 1; i <= n; i++)
a[i] = a[i - 1] + re();
for (i = n, q[1] = n + 1; i; i--)
{
while (l < r && f[q[l + 1]] <= a[q[l + 1] - 1] - a[i - 1])
l++;
f[i] = a[q[l] - 1] - a[i - 1];
h[i] = h[q[l]] + 1;
while (l <= r && a[q[r] - 1] - f[q[r]] <= a[i - 1] - f[i])
r--;
q[++r] = i;
}
printf("%d", h[1]);
return 0;
}