之前谈的都是查找某个特定值,这里主要是谈论最小化最大值和最大化最小值问题。二分问题最容易搞错的就是终止条件,区间形式,返回lb还是ub或者需不需要再加减一。这里列出了模板
一:整型数二分
1.最小化最大值问题
形式一:(ub - lb) > 1 区间为 (lb, ub] 结果为 ub
while (ub - lb > 1) { int mid = lb + (ub - lb) / 2; if (C(mid)) ub = mid; else lb = mid; }
// 终止循环时 ub == lb + 1
形式二: (ub - lb) > 0 区间为 [lb, ub] 结果为 ub
while (ub - lb > 0) { int mid = lb + (ub - lb) / 2; if (C(mid)) ub = mid; else lb = mid + 1; }
// 终止循环时 ub == lb
2.最大化最小值问题
形式一:(ub - lb) > 1 区间为 [lb, ub) 结果为 lb
while (ub - lb > 1) { int mid = lb + (ub - lb) / 2; if (C(mid)) ub = mid; else lb = mid; }
// 终止循环时 ub == lb + 1
形式二: (ub - lb) > 0 区间为 [lb, ub] 结果为 lb
while (ub - lb > 0) { int mid = lb + (ub - lb + 1) / 2; if (C(mid)) lb = mid; else ub = mid - 1; } // 终止循环时 ub == lb
最小化最大值通过维护ub的位置,最大化最小值通过维护lb的位置。
死循环的核心点在于mid的取值方式:mid = lb + (ub - lb) /2,如果某一时刻 ub == lb + 1,那么由于mid的向下取整,会导致 mid = lb; 假如 if 语句导致 lb = mid 发生,那么就会进入死循环,mid = lb,接着 lb = mid。同样的 mid = lb + (ub - lb + 1) / 2; 如果有 ub = mid操作,也有可能进入死循环。但是ub - lb > 1的终止条件会使得此情况不再发生,原因在于当ub == lb + 1时,由于不满足循环条件循环退出,也就不存在mid = lb或者 mid = ub的操作了。
当区间为闭区间,防止进入死循环的方法就是不能使mid == lb && mid == ub成立
1)最小化最大值 mid 向下取整(维护ub)
如果 if 条件成立,需要维护ub,令ub = mid; 如果不成立则令lb = mid + 1;因为mid向下取整只可能取到lb,为避免死循环就必须使lb = mid + 1;且因为我们要求的是最小化满足条件的值,既然mid不满足循环条件,也就无需考虑此值了。
2)最大化最小值 mid 向上取整(维护lb)
如果 if 条件成立,需要维护b,令lb = mid; 如果不成立则令ub = mid - 1;因为mid向上取整只可能取到ub,为避免死循环就必须使ub = mid - 1;且因为我们要求的是最大化满足条件的值,既然mid不满足循环条件,也就无需考虑此值了。
二:浮点数二分
浮点数二分就通常没有所谓的边界问题了
形式一:
for (int i = 0; i < 100; i++) { double mid = lb + (ub - lb) / 2; if (C(mid)) ub = m; // lb = m; according to problem else lb = m; // ub = m; according to problem }
100次的循环精度可达10e-30
形式二:
while (ub - lb > eps) { double mid = lb + (ub - lb) / 2; if (C(mid)) ub = m; // lb = m; according to problem else lb = m; // lb = m; according to problem }
eps可根据实际情况来设定,需要注意的是eps如果太小,因为浮点数机器实现的原因,可能会导致死循环。