题解 P1873 【砍树】

· · 题解

这题是二分答案题。它满足二分答案题的几个特征:

(1)求最大/最小值;

(2)答案离散(答案有有限种可能);

(3)容易判断答案是否正确(事实上,这题如果要写SPJ,只需要满足ans满足条件但ans+1不满足条件即可)。

二分答案题的做法即是:

(1)确定答案区间;

(2)在保证答案在区间内的前提下,逐步缩小区间;

(3)当区间缩小到仅包含一个可能解时,该可能解即为答案。

这里主要要注意第(2)点,就是如何缩小区间。这是二分的精华所在,也是二分搜索程序如此难写的原因(可以自行查一下历史,或者读一下《编程珠玑》)。

下面贴一下代码。注意数据类型要用unsigned long(为了节省空间,用了unsigned;程序中用usl代表unsigned long)。

#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取值的说明: