题解 P1873 【砍树】
Sweetlemon
2017-03-26 07:15:03
这题是二分答案题。它满足二分答案题的几个特征:
(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)