BZOJ1233 [Usaco2009Open]干草堆tower[贪心+单调队列优化]

地址


注意思路!多看几遍!

很巧妙的一道题。不再是决策点以dp值中一部分含j项为维护对象,而是通过维护条件来获取决策。

首先有个贪心策略,让底层的宽度尽可能小,才能让高度尽可能高。所以应该倒着dp,表示堆$i$~$n$的最高高度$f[i]$,同时这种最值应来源于之后的j,要在设一个$g[i]$表示以i为底层,最窄的宽度。这个的话真的只可意会啊。注意$g[i]$没人告诉你是单调的,$g[i]$之后一个不合法的决策都可能有$g[j]>g[i]$,所以单调性问题还当谨慎考虑。

所以dp方程就能出来了

$g[i]=min(sum[j-1]-sum[i-1]) $          $  g[j]<=sum[j-1]-sum[i-1]$

$f[i]=f[j]+1$

然后这个是$O(n^2)$的,考虑优化。从min内可以看出,j越小越好。那瓶颈就在于后面的那个约束条件怎么用,就是我在保证条件的情况下取j最小。

转化条件:$g[j]<=sum[j-1]-sum[i-1]$移项得$sum[j-1]-g[j]≥sum[i-1]$,而$sum[i-1]$是单调减的,那我要之前的j得插入$sum[j-1]-g[j]$,在dp到i时去把决策中大于等于$sum[i-1]$的找一个最小j。

于是单调队列维护j单调递减,$sum[j-1]-g[j]$单调减元素。假设dp完i后,我将目前i要向单调队列队尾比较。如果$sum[j-1]-g[j]>=sum[q[r]-1]-g[q[r]]$也就是j可以发挥与队尾同等的作用(甚至更优)的话,而我又渴求j尽可能小,那队尾就没什么卵用可以弹出了。直到不满足上述比较,就在队尾插入。这样保证$sum[j-1]-g[j]$单调减,我要取合法决策,便是队列头开始的一段大于等于$sum_i$的。注意,我要j最小,那队头满足条件的若干个决策,只要j最小的,那我可以依据j单调性将队头不断pop(反正之后$sum[i-1]$只会更小,现在留着也没用),直到最后一个满足条件的,就是最小的j。还是有点难以理解,贴个图。

 

然后每次就可以取到最小满足条件j了。

这个故事告诉我们单调队列的队头并不就一定是检查队头合法性的(范围之类的),也可以排除不优决策,这依赖于队头和队头下一个元素间的相互比较,要结合单调性予以考虑。

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<algorithm>
 4 #include<cstring>
 5 #include<cmath>
 6 using namespace std;
 7 typedef long long ll;
 8 template<typename T>inline char MIN(T&A,T B){return A>B?A=B,1:0;}
 9 template<typename T>inline char MAX(T&A,T B){return A<B?A=B,1:0;}
10 template<typename T>inline T _min(T A,T B){return A<B?A:B;}
11 template<typename T>inline T _max(T A,T B){return A>B?A:B;}
12 template<typename T>inline T read(T&x){
13     x=0;int f=0;char c;while(!isdigit(c=getchar()))if(c=='-')f=1;
14     while(isdigit(c))x=x*10+(c&15),c=getchar();return f?x=-x:x;
15 }
16 const int N=100000+7;
17 int sum[N],f[N],g[N],q[N];
18 int n,l,r;
19 
20 int main(){//freopen("test.in","r",stdin);//freopen("tmp.out","w",stdout);
21     read(n);for(register int i=1;i<=n;++i)sum[i]=read(sum[i])+sum[i-1];
22     l=1,r=1;q[l]=n+1;
23     for(register int i=n;i;--i){
24         while(l<r&&sum[q[l+1]-1]-g[q[l+1]]>=sum[i-1])++l;
25         g[i]=sum[q[l]-1]-sum[i-1],f[i]=f[q[l]]+1;
26         while(l<=r&&sum[q[r]-1]-g[q[r]]<=sum[i-1]-g[i])--r;
27         q[++r]=i;
28     }
29     printf("%d\n",f[1]);
30     return 0;
31 }

 

转载于:https://www.cnblogs.com/saigyouji-yuyuko/p/10453611.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值