题解 P1873 【砍树】

Sweetlemon

2017-03-26 07:15:03

Solution

这题是二分答案题。它满足二分答案题的几个特征: (1)求最大/最小值; (2)答案离散(答案有有限种可能); (3)容易判断答案是否正确(事实上,这题如果要写SPJ,只需要满足ans满足条件但ans+1不满足条件即可)。 二分答案题的做法即是: (1)确定答案区间; (2)在保证答案在区间内的前提下,逐步缩小区间; (3)当区间缩小到仅包含一个可能解时,该可能解即为答案。 这里主要要注意第(2)点,就是如何缩小区间。这是二分的精华所在,也是二分搜索程序如此难写的原因(可以自行查一下历史,或者读一下《编程珠玑》)。 下面贴一下代码。注意数据类型要用unsigned long(为了节省空间,用了unsigned;程序中用usl代表unsigned long)。 ```cpp #include <iostream> using namespace std; typedef unsigned long usl; // 本程序主要使用unsigned long bool check(usl * a, usl n, usl m, usl pos); //检查解是否可行的函数 int main(void){ usl n,m; usl l=0,r=1,mid; cin >> n >> m; usl a[n]; for (int i=0;i<n;i++){ cin >> a[i]; if (a[i]>r) r=a[i]; //r保存目前读到的最高的树的高度 } r--; //在最高的树的高度砍肯定不行啦(这样什么都没砍到),因此-1 //答案区间为:ans in [l,r];即可能的砍树高度从0(全部砍掉)到最高的树的高度-1 while (l<r){ //注意上面这个循环的条件;当l==r,也就是ans in [l,r]变成ans in [l,l]时,ans就确定下来了(ans=l) mid=(l+r+1)>>1; //即mid=(l+r+1)/2; //上面不能用mid=(l+r)/2! 因为如果r==l+1,而且check(l)为true,那么每次的mid都是l,结果l和r的值都没有改变,造成死循环 //换句话说,mid要偏向r //对于这个问题,底下有图解 if (check(a,n,m,mid)) //如果mid是可行解 l=mid; //那么最低就砍到mid,不要再低了(再低就破坏森林生态环境) else //mid不可行 r=mid-1; //那么必须得砍得比mid还要低,所以最高的高度为mid-1 } cout << l; //此时输出l和r都是一样的 return 0; } bool check(usl * a, usl n, usl m, usl pos){ usl cnt=0; for (int i=0;i<n;i++) if (a[i]>pos) cnt+=(a[i]-pos); return cnt>=m; //这里相当于return (cnt>=m)?true:false; } ``` 下面是mid取值的说明: ![](https://cdn.luogu.com.cn/upload/pic/4764.png)