BZOJ 1233 干草堆 (单调队列优化DP)

本文解析了BZOJ 1233干草堆问题,通过对比贪心与动态规划(DP)的方法,揭示了贪心策略的局限性,并详细阐述了如何利用DP及单调队列解决此问题,实现寻找最优干草堆构建方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

\(BZOJ~1233~~\)干草堆: (题目特殊性质)

ZjXP1I.png



\(solution:\)

很妙的一道题目,开始看了一眼觉得是个傻逼贪心,从后往前当前层能多短就多短,尽量节省花费。但是这是DP专题,怎么会有一道小贪心混进来?上网一搜,我果然还是太笨了!

6
11 10 7 3 2 6

这组小数据直接把贪心逼上绝路,如果顶层只有6(6-237-1011),只有三层。而如果顶层宽一点(62-37-10-11),就有四层了。什么!我之前好心节省草包,居然办了坏事?

好吧,题目限定了每一个草包都必须用,这样贪心是有后效性的(你节省的草包改变了下一阶段的状态)。于是我们只好DP,可是我们要维护的东西可就多了:(当前是第几个草包)(最下面一层多宽)(整个干草堆的高度)而题目数据范围只允许我们 \(n~logn\) ,这差距还是有点的,所以我们需要研究题目的性质。

性质: 最下面一层最窄的干草堆一定包含高度最高的最优解。

任意取出一个能使层数最高的方案,设有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 相同。证毕。  -----引用

然后我们就可发现我们的底层宽度和整个干草堆的高度是相关联的,我们可以在DP最下面一层宽度的同时记录一下高度即可。就像我们的题目变成了求干草堆底层最窄的方案(方案包含高度)。而为了更好的求底层最窄,我们可以从后往前DP,我们设 \(f[i]\) 表示到倒数第i个干草堆底层最窄的宽度。然后转移就变成了:(设 \(s[i]\)) 表示从i开始到最后一个草包的宽度之和(就是后缀和)

\(f[i]=min\{s[i]-s[j] \}\quad j>i,s[i]-s[j]>f[j]\)

然后我们发现这个东西 \(s[i]-s[j] ,s[i]-s[j]>f[j]\) 很难维护,显然我们需要 \(j\) 尽量小,但是小的 \(j\) 并不一定满足 \(s[i]\times s[j]>f[j]\)于是我们用单调队列来维护。

单调队列的 \(j\) 应该从大到小排序,只有第一个是满足\(s[i]-s[j]>f[j]\) ,在DP过程中当第二个也满足时,第一个显然失去最优效果(于是删除对首)。然后小的 \(j\) 在加入优先队列时,可以更具定义式(\(f[i]=min\{s[i]-s[j] \}\))从后往前淘汰掉比它不优的 \(j\)(具体见代码)



\(code:\)

#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>

#define ll long long
#define db double
#define rg register int

using namespace std;

int n,tt;
int a[100005];
int s[100005];
int q[100005];
int h[100005];
int f[100005];

inline int qr(){
    register char ch; register bool sign=0; rg res=0;
    while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
    while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
    if(sign)return -res; else return res;
}

int main(){
    //freopen(".in","r",stdin);
    //freopen(".out","w",stdout);
    n=qr(); q[++tt]=n+1; rg l=1,r=1;
    for(rg i=1;i<=n;++i) a[i]=qr();
    for(rg i=n;i>=1;--i) s[i]=s[i+1]+a[i];
    for(rg i=n;i>=1;--i){
        while(l<r&&s[i]>=f[q[l+1]]+s[q[l+1]])++l;
        f[i]=s[i]-s[q[l]]; h[i]=h[q[l]]+1;
        while(l<=r&&f[q[r]]+s[q[r]]>f[i]+s[i])--r;
        q[++r]=i;
    }printf("%d\n",h[1]);
    return 0;
}

转载于:https://www.cnblogs.com/812-xiao-wen/p/11212035.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值