BZOJ1233 [Usaco2009Open]干草堆tower

一道单调队列优化\(DP\)

原题链接

首先想到的应该是\(O(n^3)\)的朴素\(DP\)
定义\(f[i][j]\)表示第\(j\sim i\)块干草堆作为顶层时的最大高度。

\(\qquad\qquad f[i][j]=\max\limits_{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]\)表示第\(i\sim n\)块干草堆构成的塔的最底层的宽度,\(h[i]\)表示状态\(f[i]\)下的最大高度。

\(\qquad\qquad f[i]=\min\limits_{j=i+1}^n\{sum[j-1]-sum[i-1]\},(f[j]\leqslant sum[j-1]-sum[i-1])\)

\(\qquad\qquad h[i]=h[j]+1\)

时间复杂度\(O(n^2)\)
显然\(j\)越小越好。
\(f[i]\)的转移方程条件移项得\(sum[i-1]\leqslant sum[j-1]-f[j]\),所以对于一个决策\(k\),如果有一个决策\(j\)满足\(k>j\text{且}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;
}

ps:因为我用的是\(VS\),所以代码会自动补空格,而我本来的风格是没有空格的。

猜你喜欢

转载自www.cnblogs.com/Iowa-Battleship/p/9509844.html