算法 {二分,浮点数二分,三分}
二分
錯誤
即使二分區間不溢出, 但運算依然可能會溢出;
比如int l = -2e9, r = 2e9
, 假如答案是-2e9
那麽最後一次就是l=r=-2e9
此時mid = (l+r)/2
會導致l+r=-4e9
溢出了; 同樣假如答案是2e9
也是l+r=4e9
溢出了; 當然 假如答案是0
那麽此時不會溢出;
因此 二分的合法區間 必須要取最大值的一半, 即int l = -1e9, r = 1e9
這是沒問題的;
浮点数二分
性质
Finally,
l
≤
a
n
s
≤
r
l \leq ans \leq r
l≤ans≤r: 1
if the question asked 下取整, you should use
r
r
r; 2
if 上取整, you should use
l
l
l;
.
e.g., 102. 最佳牛围栏
算法
模板代码
{ // 浮点数二分
auto __check_success = [&]( Double _mid) -> bool{
?
};
#define __ANSWER_IN_LEFT_ ? // 如果`1` 对应为`l=mid`, 否则要定义为`0` 即`r=mid`;
Double __l = ?, __r = ?;
ASSERT_( ___SupimoTools::Double_cmp( __l, __r) == -1);
Double __ANS;
while( __r - __l >= Double(1e-?)){ // 比如題目要求精度為`1e-9` 那麼這裡就設置為`1e-9`;
__ANS = (__l + __r) / 2;
if( __check_success( __ANS)){
#if __ANSWER_IN_LEFT_
__l = __ANS;
#else
__r = __ANS;
#endif
}
else{
#if __ANSWER_IN_LEFT_
__r = __ANS;
#else
__l = __ANS;
#endif
}
}
#if __ANSWER_IN_LEFT_
__ANS = __l;
#else
__ANS = __r;
#endif
if( __check_success( __ANS)){ // `__ANS`为答案
?
}
else{
?
}
#undef __ANSWER_IN_LEFT_
} // 浮点数二分
例题
LINK: https://www.acwing.com/problem/content/submission/5051/
;
如果设置成1e-8
的精度 会进入死循环, 原因是: LINK: (https://editor.csdn.net/md/?articleId=128991752)-(@LOC_0)
;
.
即虽然a<b
, 但是(a+b)/2
并不是>a && <b
的, 而是会等于a/b
; 此时, 就掉入死循环了;
解决办法是:
.
要么: 使用double
精度设置成Tools::__DoubleEpsilon_ = 1e-6
(因为题目就是说精度是1e-6
, 那你就把让while( r - l >= 1e-6)
即可; 你设置那么高的精度干嘛…)
.
要么: 使用long double
, 此时精度可以设置成1e-8
;
.
参见: LINK: (https://editor.csdn.net/md/?not_checkout=1&articleId=128991752)-(@LOC_1)
;
整数二分
算法
模板代码
{ // 整数二分
auto __check_success = []( long long _mid) -> bool{
@TODO;
};
#define __ANSWER_IN_LEFT_ @TODO // 如果`1` 对应为`l=mid`, 否则要定义为`0` 即`r=mid`;
long long __l = @TODO, __r = @TODO;
ASSERT_( __l <= __r);
long long __mid;
while( __l < __r){
__mid = (__l + __r + __ANSWER_IN_LEFT_) >> 1;
if( __check_success( __mid)){
#if __ANSWER_IN_LEFT_
__l = __mid;
#else
__r = __mid;
#endif
}else{
#if __ANSWER_IN_LEFT_
__r = __mid - 1;
#else
__l = __mid + 1;
#endif
}
}
//>< `l == r`;
if( __check_success( __l)){
@TODO;
}
else{
@TODO;
}
#undef __ANSWER_IN_LEFT_
} // 整数二分
性质
最终的布尔二段式 比如是: calc( < ans) < K; calc( >= ans) >= K
那么, 你的calc( x)
函数, 假如他返回值是[0, 1e9]
, 那么 你不必要非得 让他按照定义的来,
比如, calc函数的执行过程是: 一个个累加, 那么, 返回值是1e9 这肯定是超时的…
而假如K只有1e5, 那么, calc函数 返回一个1e9, 和 返回一个1e5(K), 是完全等价的!
因为, 我们并不关心, 他返回值具体是多少, 只是判断 到底是(< K) 还是 (>= K)
因此, 在calc函数里, 一旦累加到K
, 就可以返回了!
具体例题:
错误
#求满足条件的最小/最大值, 不一定就是二分;#
比如他是[x, x, x, x, v, x, v, x, v, x]
, 第一个v
他是4
即答案是4
; 但他不符合二分;
@DELI;
succ = false;
while( l < r){
int mid = (l + r) >> 1;
if( Check( mid)){
succ = true;
r = mid;
}
else{
l = mid + 1;
}
}
if( false == succ){
cout<< "No Solution";
return;
}
cout<< r;
Above code is wrong, the answer (i.e., r
) maybe not detected in the process while( l < r){}
.
For example, l = 1, r = 2
(the answer is 2
), so mid = 1
, we get check( 1) = false
so l = r = 2
, then the process while(){}
has terminated; however, we have not test check( 2)
;
So, the standard code is
while( l < r){
int mid = (l + r) >> 1;
if( Check( mid)){
r = mid;
}
else{
l = mid + 1;
}
}
if( false == Check( r)){
cout<< "No Solution";
return;
}
cout<< r;
@Delimiter