0x50 动态规划(练习)20:干草堆(题解)

题意

题目链接

【题意】
 奶牛们讨厌黑暗。
 为了调整牛棚顶的电灯的亮度,Bessie必须建一座干草堆使得她能够爬上去够到灯泡。
 一共有N大包的干草(从1到N编号)依靠传送带连续的传输进牛棚来。
 第i包干草有一个宽度Wi。
 所有的干草包的厚度和高度都为1。
Bessie必须利用所有N包干草来建立起干草堆。
 她可以想放多少包就放多少包来建立起草堆的地基(当然是紧紧的放在一行中)。
 接下来她可以将下一个草包放在之前一级的上方来建立新的一级。
 注意:每一级不能比下面的一级宽。
 她持续的这么放置,直到所有的草包都被安置完成。
 她必须按照草包进入牛棚的顺序堆放草包。
 说得更清楚一些:一旦她将一个草包放在第二级 ,那么她不能将接下来的草包放在第一级上。
Bessie的目标是建立起最高的草包堆。 
 【输入格式】
 第1行:一个整数N。
 第2..N+1行:第i+1行包含整数Wi。 
 【输出格式】
 输出一个整数,表示草包堆的最高的高度。 
 【数据范围】
1≤N≤100000,
 1≤Wi≤10000 
【输入样例】
3
 1
 2
 3
【输出样例】
2 

题解

第一眼看到这个题目,WOW,大水题,怕不是一道SB的贪心,出题人是不是S,为什么要用DP。

只要顶层最小不就可以让层数最多了吗?

于是当快乐的打完代码,发现WA了,马上大骂出题人数据错了。

下载数据看到这一组:

6
9 8 2 1 5 5 

╭(╯^╰)╮,肯定错了,用我们的方法一看:|9 8 2 1|5|5|,三组呀,哪来的四组?不对!|9|8|2 1 5|5|!!!!

出题人:你大爷还是你大爷。。。

没错我们其实可以发现顶层最小有可能因为下面的一些小数字导致第二层很大,然后陷入恶性循环,有时候我们其实可以把一些小数字放到上面一层的。

于是我们又蹦出一个思路,难道是下面一层最小且可以堆起来的话,层数一定是最大的?

虽然不能证明但是就是感觉是对的,上网一查,ZKW巨佬已经证明了(一下做了一点点修改,方便看懂QMQ):
任意取出一个能使层数最高的方案,设有\(C_A\)层,把其中从下往上每一层最大的块编号记为\(A_i\);任取一个能使底边最短的方案,设有\(C_B\)层(定义\(C_B<C_A\),这样可以严格证明一定存在一个\(k\)),把其中从下往上每一层最大的块编号记为\(B_i\)。 显然\(A_{C_B}<=B_{C_B}\),这说明至少存在一个\(k∈(1,C_B)\),满足\(A_{k-1}≥B_{k-1}\)且Ak≤Bk。 也就是说,方案 A 第K 层完全被方案 B 第K 层包含。 构造一个新方案,第K 层往上按方案 A,往下按方案 B, 两边都不要的块放中间当第K 层。 新方案的层数与 A 相同,而底边长度与 B 相同。证毕。
在这里插入图片描述

UPDATA:(CLB奆佬想的证明):
对于一组数据而言(反着想,从上往下堆干草堆),假设\(i-1\)个干草堆已经证明了此性质,对于\(i\)从右到左找到第一个可以满足下一层大于等于上一层的,不难发现\(i\)也是满足的。(同时多加入新的干草堆不会使层数减少。)

那么我们就知道底层最小层数最小,而剩下的也是一直遵循底层最小来的。

那么我们就可以把数组倒过来,即往下叠,\(f[i]\)表示的是\(1-i\)最多叠几层,且\(i\)所在的层的宽度最小化,为\(g[i]\),而\(s[i]自然就是前缀和了\)

那么自然而然DP方程为:\(f[i]=max(f[j]+1)(j<i\)&&\(s[i]-s[j]>=g[j])\),这时候就要问了,为什么这样不怕后效性,即层数比\(f[i]\)小,但是底下一层也不\(g[i]\)小的一种方案,那么就会有后效性,但是不会的,底下一层最小就是层数最多的情况,那么只存在层数比\(f[i]\)小,但是底下一层也不\(g[i]\)大的情况,这个方案会有后效性?

我们将\(s[i]-s[j]>=g[j]\)化一下:\(g[j]+s[j]<=s[i]\)\(s[i]\)单调递增,那么假设存在\(k<j\),且\(g[k]+s[k]>g[j]+s[j]\),因为如果\(k\)满足要求,\(j\)也是满足要求的,而且\(f[k]<=f[j]\),那么\(k\)就是不会被继承的。

那么维护一个\(g+s\)递增的序列,但是如何踢队头?如果\(head+1\)满足情况就踢\(head\),因为越后面的\(f\)越大。

#include<cstdio>
#include<cstring>
#define  N  110000
using  namespace  std;
int  list[N],head,tail;
int  n,s[N],g[N]/*第i层的宽度*/,f[N];
int  main()
{
	scanf("%d",&n);
	for(int  i=1;i<=n;i++)scanf("%d",&s[n-i+1]);
	for(int  i=2;i<=n;i++)s[i]+=s[i-1];
	head=tail=1;
	for(int  i=1;i<=n;i++)
	{
		while(head<tail  &&  g[list[head+1]]+s[list[head+1]]<=s[i])head++;
		f[i]=f[list[head]]+1;g[i]=s[i]-s[list[head]];
		while(head<=tail  &&  g[list[tail]]+s[list[tail]]>=g[i]+s[i])tail--;
		list[++tail]=i;
	}
	printf("%d\n",f[n]);
	return  0;
}
posted @ 2019-10-21 15:25  敌敌畏58  阅读(333)  评论(0)    收藏  举报