二分
应用:当问题的答案具有单调性时,可以通过二分把求解转化为判定
复杂度理论:判定难度小于求解
效率提升本质:利用单调性,通过一次比较即可批量判断解是否符合条件
整数集合上的二分
规定:
1.答案位于 [ l , r ] [l,r] [l,r]中
注: [ l , r ] [l,r] [l,r]为当前能确认含有答案的准确区间,即 l , r l,r l,r符合条件,而 l − 1 , r + 1 l-1,r+1 l−1,r+1不符合条件
2.每次循环后区间缩小
3.条件为 m i d mid mid符合条件
分类:
a.若 m i d mid mid符合条件,则区间向左缩小,如图:
由1知,当 m i d mid mid符合条件时, [ l , r ] → [ l , m i d ] [l,r]\rightarrow[l,mid] [l,r]→[l,mid];当 m i d mid mid不符合条件时, [ l , r ] → [ m i d + 1 , r ] [l,r]\rightarrow[mid+1,r] [l,r]→[mid+1,r]
由2知, { m i d < r , m i d + 1 > l ⇔ l − 1 < m i d < r \left\{\begin{array}{ll}mid<r,\\mid+1>l\end{array}\right.\Leftrightarrow l-1<mid<r {mid<r,mid+1>l⇔l−1<mid<r
由于我们要进行二分操作, m i d mid mid应尽量与 l + r 2 \dfrac{l+r}{2} 2l+r靠近,又由于 m i d ∈ Z mid\in \mathbb{Z} mid∈Z,故问题转化为选择 ⌊ l + r 2 ⌋ \lfloor\dfrac{l+r}{2}\rfloor ⌊2l+r⌋还是 ⌈ l + r 2 ⌉ \lceil\dfrac{l+r}{2}\rceil ⌈2l+r⌉作为 m i d mid mid的值。
易知 x − 1 < ⌊ x ⌋ ⩽ x x-1<\lfloor x\rfloor\leqslant x x−1<⌊x⌋⩽x,故选择 m i d = ⌊ l + r 2 ⌋ mid=\lfloor\dfrac{l+r}{2}\rfloor mid=⌊2l+r⌋时有 l + r 2 − 1 < m i d ⩽ l + r 2 \dfrac{l+r}{2}-1<mid\leqslant\dfrac{l+r}{2} 2l+r−1<mid⩽2l+r,
由1知, l < r l<r l<r,故 m i d > l + r 2 − 1 > l + l 2 − 1 = l − 1 , m i d ⩽ l + r 2 < r + r 2 = r mid>\dfrac{l+r}{2}-1>\dfrac{l+l}{2}-1=l-1,mid\leqslant\dfrac{l+r}{2}<\dfrac{r+r}{2}=r mid>2l+r−1>2l+l−1=l−1,mid⩽2l+r<2r+r=r,
即 l − 1 < m i d < r l-1<mid<r l−1<mid<r,符合条件。
实现:
while (l < r) { // 1.
int mid = (l + r) >> 1; // 2.
if (check(mid)) r = mid;
else l = mid + 1;
}
细节:
1.终止时 l = r l=r l=r,则 l ⩽ a n s ⩽ r = l ⇔ a n s = l = r l\leqslant ans\leqslant r=l\Leftrightarrow ans=l=r l⩽ans⩽r=l⇔ans=l=r
2. ⌊ l + r 2 ⌋ \lfloor\dfrac{l+r}{2}\rfloor ⌊2l+r⌋应写作(l+r)>>1而不是(l+r)/2.
略证:考虑奇数 S ( S > 0 ) S(S>0) S(S>0),要证 ( − S ) > > 1 = ⌊ − S 2 ⌋ (-S)>>1=\lfloor\dfrac{-S}{2}\rfloor (−S)>>1=⌊2−S⌋,只要证 − S ‾ − 1 = − S − 1 \overline{-S}-1=-S-1 −S−1=−S−1,
而 0 − ( − S ‾ − 1 ) = [ 2 n − ( − S ‾ ) ] + 1 = S + 1 0-(\overline{-S}-1)=[2^n-(\overline{-S})]+1=S+1 0−(−S−1)=[2n−(−S)]+1=S+1,即证。
c++整数除法向0取整。
b.若 m i d mid mid符合条件,则区间向右缩小,如图:
由1知,当 m i d mid mid符合条件时, [ l , r ] → [ m i d , r ] [l,r]\rightarrow[mid,r] [l,r]→[mid,r];当 m i d mid mid不符合条件时, [ l , r ] → [ l , m i d − 1 ] [l,r]\rightarrow[l,mid-1] [l,r]→[l,mid−1]
由2知, { m i d > l , m i d − 1 < r ⇔ l < m i d < r + 1 \left\{\begin{array}{ll}mid>l,\\mid-1<r\end{array}\right.\Leftrightarrow l<mid<r+1 {mid>l,mid−1<r⇔l<mid<r+1
易知 x ⩽ ⌈ x ⌉ < x + 1 x\leqslant\lceil x\rceil< x+1 x⩽⌈x⌉<x+1,故选择 m i d = ⌈ l + r 2 ⌉ mid=\lceil\dfrac{l+r}{2}\rceil mid=⌈2l+r⌉时有 l + r 2 ⩽ m i d < l + r 2 + 1 \dfrac{l+r}{2}\leqslant mid<\dfrac{l+r}{2}+1 2l+r⩽mid<2l+r+1,
由1知, l < r l<r l<r,故 m i d ⩾ l + r 2 > l + l 2 = l , m i d < l + r 2 + 1 < r + r 2 = r + 1 mid\geqslant\dfrac{l+r}{2}>\dfrac{l+l}{2}=l,mid<\dfrac{l+r}{2}+1<\dfrac{r+r}{2}=r+1 mid⩾2l+r>2l+l=l,mid<2l+r+1<2r+r=r+1,
即 l < m i d < r + 1 l<mid<r+1 l<mid<r+1,符合条件。
实现:
while (l < r) {
int mid = (l + r + 1) >> 1; // 1.
if (check(mid)) l = mid;
else r = mid - 1;
}
细节:
1.由于上取整不易直接使用下取整来表示,我们先对可能的结果进行讨论:若 x 2 ∈ Z \dfrac{x}{2}\in \mathbb{Z} 2x∈Z,则 ⌈ x 2 ⌉ = x 2 \lceil\dfrac{x}{2}\rceil=\dfrac{x}{2} ⌈2x⌉=2x;若 x 2 ∈ Q \dfrac{x}{2}\in \mathbb{Q} 2x∈Q,则 x 2 \dfrac{x}{2} 2x可写作 n . 5 ‾ \overline{n.5} n.5的形式, ⌈ x 2 ⌉ = ⌊ x 2 ⌋ + 1 \lceil\dfrac{x}{2}\rceil=\lfloor\dfrac{x}{2}\rfloor+1 ⌈2x⌉=⌊2x⌋+1.
不难发现,此时 ⌈ x 2 ⌉ = [ x 2 ] \lceil\dfrac{x}{2}\rceil=[\dfrac{x}{2}] ⌈2x⌉=[2x],对 x 2 \dfrac{x}{2} 2x进行四舍五入即可求得 ⌈ x 2 ⌉ \lceil\dfrac{x}{2}\rceil ⌈2x⌉的值。
而 [ x ] = ⌊ x + 0.5 ⌋ [x]=\lfloor x+0.5\rfloor [x]=⌊x+0.5⌋( { x } ⩾ 0.5 \{x\}\geqslant0.5 {x}⩾0.5将进位),故 ⌈ l + r 2 ⌉ = [ l + r 2 ] = ⌊ l + r 2 + 0.5 ⌋ = ⌊ l + r + 1 2 ⌋ \lceil\dfrac{l+r}{2}\rceil=[\dfrac{l+r}{2}]=\lfloor\dfrac{l+r}{2}+0.5\rfloor=\lfloor\dfrac{l+r+1}{2}\rfloor ⌈2l+r⌉=[2l+r]=⌊2l+r+0.5⌋=⌊2l+r+1⌋,写作(l+r+1)>>1.
值得注意的是,我们认为答案位于 [ l , r ] [l,r] [l,r]中,事实上已经假设有解;若 [ 1 , n ] [1,n] [1,n]中无解,以符合便向左缩小情况为例,每一个 m i d mid mid都不符合条件,答案区间最终会向右缩小至n,而我们并没有判断n是否符合条件。一方面,我们可以特判n,另一方面,我们可以将求解范围扩展至 [ 1 , n + 1 ] [1,n+1] [1,n+1],这样,当无解时范围将会被缩小至n+1,显然超出求解范围,便能快速判断是否无解。
符合便向右缩小情况类似,扩展至 [ 0 , n ] [0,n] [0,n]即可。
总结:
左 | 右 | |
---|---|---|
mid符合 | r = mid | l = mid |
mid不合 | l = mid+1 | r = mid-1 |
mid取值 | ⌊ l + r 2 ⌋ \lfloor\dfrac{l+r}{2}\rfloor ⌊2l+r⌋ | ⌈ l + r 2 ⌉ = [ l + r 2 ] \lceil\dfrac{l+r}{2}\rceil=[\dfrac{l+r}{2}] ⌈2l+r⌉=[2l+r] |
代码实现 | (l+r)>>1 | (l+r+1)>>1 |
无解判定 | [ 1 , n + 1 ] [1,n+1] [1,n+1] | [ 0 , n ] [0,n] [0,n] |
简记:左下右上
c++ STL中lower_bound与upper_bound函数实现了在一个序列中二分查找某个整数x的后继。
问题写法:
1. l = m i d + 1 , r = m i d − 1 l=mid+1,r=mid-1 l=mid+1,r=mid−1:符合便向左缩小时 r r r缩过头导致 m i d mid mid丢失;符合便向右缩小时 l l l缩过头导致 m i d mid mid丢失
2. l = m i d , r = m i d l=mid,r=mid l=mid,r=mid:符合便向左缩小时 l l l未缩到位导致 m i d + 1 mid+1 mid+1混入;符合便向右缩小时 r r r未缩到位导致 m i d − 1 mid-1 mid−1混入
实数域上的二分
实现1:精度控制
while (l + eps < r) { // 1.
double mid = (l + r) / 2; // 2.
if (check(mid)) r = mid;
else l = mid;
}
细节:
1.eps为精度,即 r − l < e p s r-l<eps r−l<eps,若保留k位小数,一般取 e p s = 1 0 − ( k + 2 ) eps=10^{-(k+2)} eps=10−(k+2),此时出现 l + e p s l+eps l+eps发生进位的概率极小,几乎能求出正确结果。
2.此处 l , r l,r l,r为浮点数,做实数运算。
实现2:固定次数
const int K = 100;
for (int i = 0; i < K; i++) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
三分法求单峰函数极值
定义:拥有唯一极值点,且极值点两侧严格单调
思路:分类讨论
以单峰函数为例,在定义域 [ l , r ] [l,r] [l,r]任取两点 l m i d , r m i d ( l m i d < r m i d ) lmid,rmid(lmid<rmid) lmid,rmid(lmid<rmid),则
当 f ( l m i d ) < f ( r m i d ) f(lmid)<f(rmid) f(lmid)<f(rmid)时,如图:
由于 r m i d rmid rmid位置不确定,而 l m i d lmid lmid一定在 x 0 x_0 x0左侧(由 l m i d < r m i d lmid<rmid lmid<rmid限制),故利用 l m i d < x 0 lmid<x_0 lmid<x0确定极值点比 l m i d lmid lmid大,将 l l l缩小至 l m i d lmid lmid;
当 f ( l m i d ) > f ( r m i d ) f(lmid)>f(rmid) f(lmid)>f(rmid)时,如图:
同理,利用 r m i d > x 0 rmid>x_0 rmid>x0将 r r r缩小至 r m i d rmid rmid;
当 f ( l m i d ) = f ( r m i d ) f(lmid)=f(rmid) f(lmid)=f(rmid)时,如图:
由于该函数在极值点两侧具有严格单调性,故 l m i d , r m i d lmid,rmid lmid,rmid一定位于 x 0 x_0 x0两侧,可将 l l l缩小至 l m i d lmid lmid, r r r缩小至 r m i d rmid rmid.
在此基础上,取 l m i d , r m i d lmid,rmid lmid,rmid为 [ l , r ] [l,r] [l,r]三等分点,每次将范围缩小 1 3 \dfrac{1}{3} 31,或取 l m i d , r m i d lmid,rmid lmid,rmid靠近中点,可将范围近似缩小 1 2 \dfrac{1}{2} 21,在 O ( l o g n ) O(logn) O(logn)的时间中求得最值。
二分答案转化为判定
二分的本质
二分的本质是寻找被二分量对应的合法与不合法的边界处的合法值。
为了证明这一性质,我们先对上述表述进行简化:
1.将 [ l , r ] [l,r] [l,r]中每一个数以 c h e c k ( i ) = 0 / 1 check(i)=0/1 check(i)=0/1代替(未进行判定的用?代替),表示取该值时是否合法;
2.将符合便向左缩小情况记作 + 1 +1 +1,将符合便向右缩小情况记作 1 + 1+ 1+.
对于 + 1 +1 +1型,我们先考虑一般情况,即判定过程中即有符合情况,又有不符合情况。在最后一次区间缩小前,应为 0 [ 1 , 1 ] 0[1,1] 0[1,1].接着计算出 c h e c k ( m i d ) = 1 check(mid)=1 check(mid)=1(下取整,左侧1),缩小后得到 0 [ 1 ] 1 0[1]1 0[1]1,即求得 0 , 1 0,1 0,1边界处对应为1的值。
再考虑特殊情况:
当判定过程仅有符合情况时,最后一次缩小前为 [ ? , 1 ] [?,1] [?,1].接着将会计算并确定?的值,如果 ? = 0 ?=0 ?=0,缩小后得到 0 [ 1 ] 0[1] 0[1],符合;如果 ? = 1 ?=1 ?=1,缩小后得到 [ 1 ] 1 [1]1 [1]1,为最左边的符合条件的值。
当判定过程仅有不符情况时,最后一次缩小前为 0 [ ? , ? ] 0[?,?] 0[?,?].接着将会计算并确定左侧?的值,如果 ? = 1 ?=1 ?=1,缩小后得到 0 [ 1 ] ? 0[1]? 0[1]?,符合;如果 ? = 0 ?=0 ?=0,缩小后得到 0 [ ? ] 0[?] 0[?],需进行特判,与上述无解处理一致。
对于 1 + 1+ 1+型,同理,一般情况时最后一次缩小前为 [ 1 , 1 ] 0 [1,1]0 [1,1]0,缩小为 1 [ 1 ] 0 1[1]0 1[1]0,符合;
仅有符合情况时,最后一次缩小前为 [ 1 , ? ] [1,?] [1,?],接着确定?的值, ? = 0 ?=0 ?=0缩小为 [ 1 ] 0 [1]0 [1]0,符合; ? = 1 ?=1 ?=1缩小为 1 [ 1 ] 1[1] 1[1],为最右边的符合条件的值;
仅有不符情况时,最后一次缩小前为 [ ? , ? ] 0 [?,?]0 [?,?]0.接着确定右侧?的值, ? = 1 ?=1 ?=1缩小为 ? [ 1 ] 0 ?[1]0 ?[1]0,符合; ? = 0 ?=0 ?=0缩小为 [ ? ] 0 [?]0 [?]0,需进行特判,与上述无解处理一致。
这样我们就证明了二分的本质。
我们可以定义广义的二分单调性,即通过对某一点情况的考察,能判断解位于该点左侧或右侧。
在此基础上,对于一个不具有一般意义下的单调性问题,我们将1用 ↗ ↗ ↗代替,0用 ↘ ↘ ↘代替,可将其图示为(以 + 1 +1 +1型为例):
不难发现,二分法事实上求得的是极值点,且 + 1 +1 +1型求得的是极小值点, 1 + 1+ 1+型求得的是极大值点。
进而推广到 + 1 +1 +1型求得的是一个形如 0 [ 1 ] 0[1] 0[1]的分界,而 1 + 1+ 1+型求得的是一个形如 [ 1 ] 0 [1]0 [1]0的分界;更一般的,1,0不一定为合法状态和非法状态,可以为任意的两个状态。
最优化问题
最优化问题中最终价值具有单调性(较小合法、过大非法或较大合法、过小非法),所以我们对最终价值进行二分,进而求得最优化的价值。
在这一过程中,我们将整个问题转化为了判定最终价值是否合法,即二分答案转化为判定。
例1 Best Cow Fences
思路:二分答案+前缀和
当最终价值(平均数)过小或过大时都非法,形成 ↗ ↘ ↗↘ ↗↘形式的单调性,故可用二分法(1+型)求最值。
判断二分形式:1.表达单调性 2.将极值点记作+,将 ↗ ↗ ↗记作1
对最终价值进行二分,问题转化为判定是否存在一个长度不小于F的连续子段,使得其平均数大于等于给定的二分值,即 1 n ∑ a i ⩾ a v g \dfrac{1}{n}\sum a_i\geqslant avg n1∑ai⩾avg。
为了便于计算,我们将等式右边化为0,即
∑ a i ⩾ n ∗ a v g ⇔ ∑ ( a i − a v g ) ⩾ 0 \sum a_i\geqslant n*avg\Leftrightarrow\sum(a_i-avg)\geqslant0 ∑ai⩾n∗avg⇔∑(ai−avg)⩾0
故把数列中每一个数减去二分值,并判断是否存在一个长度不小于F的连续子段,子段和非负。
部分和使用前缀和简化计算,取 a j + 1.. i ( i ⩾ j + F ) a_{j+1..i}(i\geqslant j+F) aj+1..i(i⩾j+F),则
max ∑ k = j + 1 i a k = max F ⩽ i ⩽ n { S i − min 0 ⩽ j ⩽ i − F S j } \max\sum\limits_{k=j+1}^i a_k=\max\limits_{F\leqslant i\leqslant n}\{S_i-\min\limits_{0\leqslant j\leqslant i-F}S_j\} maxk=j+1∑iak=F⩽i⩽nmax{Si−0⩽j⩽i−FminSj}
并且 min 0 ⩽ j ⩽ i − F S j \min\limits_{0\leqslant j\leqslant i-F}S_j 0⩽j⩽i−FminSj可随i变化动态更新。
实现:
bool check(double avg) {
double ret = -1, tmp = INT_MAX;
for (int i = 1; i <= n; i++)
cur = a[i] - avg, s[i] = cur + s[i - 1];
for (int i = f; i <= n; i++)
tmp = min(tmp, s[i - f]), ret = max(ret, s[i] - tmp);
return ret >= 0;
}
int main() {
double l = 1, r = INF, eps = 1e-5; // 保留三位小数
cin >> n >> f;
for (int i = 1; i <= n; i++)
cin >> a[i];
while (l + eps < r) {
double mid = (l + r) / 2;
if (check(mid))
l = mid;
else
r = mid;
}
cout << (int)(r * 1000) << endl;
}
例2 Innovative Business
思路:二分答案
本题即要求有向完全图(竞赛图)的Hamilton路径(不重不漏经过所有点的路径)。
先证本题有解,即任意竞赛图都存在Hamilton路径。
下对图中节点数 n n n用数学归纳法:
当 n = 1 , 2 n=1,2 n=1,2时,显然;
当 n ⩾ 3 n\geqslant3 n⩾3时,假设 n = k n=k n=k时结论成立,即 k k k点的竞赛图存在Hamilton路径;
当 n = k + 1 n=k+1 n=k+1时,有以下三种情况:
1.第k+1点有一条指向原Hamilton路径起点的出边,如图:
那么我们直接将它作为新Hamilton路径的起点;
2.第k+1点有一条被原Hamilton路径终点指向的入边,如图:
那么我们直接将它作为新Hamilton路径的终点;
3.若以上两种情况都不符合,即第k+1点被原Hamilton路径起点指向的入边,有指向原Hamilton路径终点的出边,如图:
方便起见,我们将指向第k+1点的入边记作0,由第k+1点发出的出边记作1,问题转化为证明0-1串 0 … 1 0…1 0…1中存在相邻两数为 01 01 01.
采用反证法,若该0-1串中任意相邻两数都相等,易知此时该串末尾的数为0,矛盾!
故存在相邻两数不等,且从左往右第一个出现的组合一定为 01 01 01(两数之前都是0),即证。
那么我们只要将第k+1点连入涉及到的两点之间即可。
由归纳原理知,结论成立。
回到原题,仿照上述数归思路,我们可以逐个确定元素的位置,将第k+1个元素放入已排好序的前k个元素中去。由于放置点为出边状态和入边状态的边界,具有 ↘ ↗ ↘↗ ↘↗的局部单调性,故可用二分法(+1型)求解。
实现:
vector<int> specialSort(int N) {
vector<int> ans;
ans.push_back(1);
if (N == 1)
return ans;
if (compare(1, 2))
ans.push_back(2);
else
ans.insert(ans.begin(), 2);
if (N == 2)
return ans; // 特判n = 1, 2
for (int i = 3; i <= N; i++) {
int l = 1, r = i; // 1.
while (l < r) {
int mid = (l + r) >> 1;
if (compare(i, ans[mid - 1])) // 下标从0开始
r = mid;
else
l = mid + 1;
}
ans.insert(ans.begin() + l - 1, i); // 2.
}
return ans;
}
细节:
1.此处需对“无解”情况进行处理:本题实际上将某数插入至 0 ↓ 1 0\downarrow1 0↓1处,仅有符合情况时,会插入至 ↓ 1 \downarrow1 ↓1处,符合条件;而仅有不符情况时,会插入至 0 ↓ 0 0\downarrow0 0↓0处(未对末位0作判定)导致结果出错,故需向右拓宽区间,使得此情况能插入至 0 ↓ 0\downarrow 0↓处。
2.向vector中插入某元素: v . i n s e r t ( v . b e g i n ( ) + p o s , v a l ) ; v.insert(v.begin() + pos, val); v.insert(v.begin()+pos,val);将val插入至第pos位(下标从0开始)前(使val放置在第pos位)。