2.积木大赛
(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。
根據題意,所形成的應該是一個嚴格的金字塔形狀,但是可能兩邊不完整,因為如果原本就有的高度超過所需高度就不需要在往左/右搭建剩餘的部分了,如樣例
首先想到枚舉所有點,然後發現高度顯然具有單調性,所以可以二分在枚舉
但對於每個點都需要O(n)的找它的左右邊界,複雜度n*n*log(n)
然而每次都找左右邊界太浪費時間,所以想辦法優化
其實這個高度是滿足單調性的,例如當從某點移動到它左邊的點時,它的左邊界是單調不增(不向右移動)的,這樣就可以單調隊列搞一下
直接以O(n)的時間預處理了所有點的左右邊界,複雜度O(nlog(n))
#include<iostream> #include<cstdio> #include<cmath> #define ll long long using namespace std; const int maxn=100009; int n,m,h[maxn],maxx; long long sum[maxn]; int L[maxn],R[maxn];//i点的左边界和右边界位置 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--;//x-(i-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); // long long y=x*x-((b+1)*b/2)-(a+1)*a/2; // if(y-(sum[R[i]-1]-sum[L[i]])<=m)return 1; ll y=(ll)x*(ll)x-(ll)(b+1)*(ll)b/2ll-(ll)(a+1)*(ll)a/2ll; if(y-(ll)(sum[R[i]-1]-sum[L[i]])<=(ll)m)return true; } return 0; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&h[i]),sum[i]=sum[i-1]+h[i],maxx=max(maxx,h[i]); int l=maxx+1,r=(int)(sqrt(m)+1.00)+maxx+1; while(l<r){ int mid=l+r>>1; if(check(mid))l=mid+1; else r=mid; } printf("%d",l-1); }