1.积木大赛
(block.pas/c/cpp)
【问题描述】
为了庆祝国庆,厦门一中举办了一年一度的“积木大赛”。
在2013年NOIP大赛中,夏夏同学己经搭建了宽度为n的大厦,其中第i块高度为hi。今年比赛的内容是对其NOIP2013搭建大厦进行扩建,使用的材料也都是体积为1正方体积木。
今年搭建的规则是:如果要在某一个位置上放一个积木,必须满足它的左下、下方、右下都有积木(用二维坐标a表示,如果要在a[i,j]位置放积木,那么a[i-1,j-1]、a[i,j-1]、a[i+1,j-1]必须要有积木)。
如果搭的积木大厦越高,夏夏同学就会觉得越有成就感,现有m个积木,问你能搭建的最大高度是多少?
【输入】
第一行两个用空格隔开的整数n和m,分别表示己搭好的宽度和可以使用的积木数量。
后面有n行,每行一个整数hi表示己搭建的第i列积木的高度。
【输出】
一个整数,表示能搭建的最大高度。
【输入输出样例】
|
样例1 |
样例2 |
||
|
block.in |
block.out |
block.in |
block.out |
|
8 4 3 4 2 1 3 3 2 4 |
5 |
3 100 3 3 3 |
4 |
【数据说明】
30%的数据满足:n<=10;m<=1000。
50%的数据满足:n<=100;m<=1000,000。
70%的数据满足:n<=1000;m<=10,000,000。
80%的数据满足:n<=10,000;m<=100,000,000。
100%的数据满足:n<=100,000;m<=1000,000,000;1<=hi<=100000。
二分答案+双指针扫描
二分搭建的高度,最低为maxh+1,最高为maxh+sqrt(m)+1
如何在O(n)时间内check呢?
对于搭建积木,我们要搭出一个金字塔形,但是,并不是要搭建整个金字塔形,有时候只要搭建部分即可,如图:
我们仅需搭建绿色部分,而不需要搭建蓝色方框内的整个金字塔
如何找到绿色部分呢?
设l为绿色部分的左边界,r为绿色部分的右边界,x为当前要搭建的金字塔顶,h为大金字塔塔高
我们需要在搭建的金字塔所需积木=整个大金字塔所需积木-黄色部分所需积木-紫色部分所需积木-棕色部分所需积木
设黄色部分高为a,则a=h-(x-l)(即图中6-1=5),黄色部分所需积木为a*(a+1)/2。
同理,能算出紫色部分所需积木。
棕色部分所需积木=r前面所有已有积木-l前面所有已有积木。我们想到了什么?前缀和
这样,绿色部分所需积木就算出来了,我们比较它与m的大小关系即可
等等!还没说l和r怎么求呢?
我们发现,对于一个位置x,如果能找到它所对的l,那么对于位置x-1,它的l必≤位置x所对的l,且从位置x所对的l ~x-1绝对不满足能做绿色部分的左边框。因为在向左移的过程中,左边的每个位置的h的要求总是增大的(金字塔形)
所以l具有单调性。我们从n到1扫描每个位置,如果当前的l不满足h[l]<h-(i-l)(即不能做绿色部分的左边框),那么我们l--,直到满足要求,然后我们拿一个数组记下每个位置的l
对r也是一样,只要从左往右扫描即可。
注意:前缀和要开long long,二分答案的下限一定要从maxh+1开始(不然会引发l==r==i或l>i或r<i等奇奇怪怪的事故),l和r不能小于1或大于n(超出给定范围n外的位置不能放积木)
#include<iostream> #include<cstdio> #include<cmath> using namespace std; typedef long long ll; int h[100001],Max=0; long long bb[100001];int n,m; int L[100001],R[100001]; bool check(int x) { int l=n,r=1; for(int i=n;i>=1;i--){while(h[l]<x-(i-l)&&l>=1)l--;L[i]=l;} for(int i=1;i<=n;i++){while(h[r]<x-(r-i)&&r<=n)r++;R[i]=r;} for(int i=1;i<=n;i++) { if(R[i]==n+1||L[i]==0)continue; int a=x-(i-L[i]),b=x-(R[i]-i); ll y=(ll)x*(ll)x-(ll)(b+1)*(ll)b/2ll-(ll)(a+1)*(ll)a/2ll; if(y-(ll)(bb[R[i]-1]-bb[L[i]])<=(ll)m)return true; } return false; } int main() { // freopen("block.in","r",stdin);freopen("block.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d",&h[i]);Max=max(Max,h[i]);bb[i]=bb[i-1]+(ll)h[i]; } int l=Max+1,r=(int)(sqrt(m)+1.00)+Max+1; while(l<r) { int mid=(l+r)/2; if(check(mid))l=mid+1; else r=mid; } cout<<l-1; return 0; }