算法竞赛进阶指南读书笔记——0x04二分

二分

应用:当问题的答案具有单调性时,可以通过二分把求解转化判定

复杂度理论:判定难度小于求解

效率提升本质:利用单调性,通过一次比较即可批量判断解是否符合条件

整数集合上的二分

规定:

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 l1,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>ll1<mid<r

由于我们要进行二分操作, m i d mid mid应尽量与 l + r 2 \dfrac{l+r}{2} 2l+r靠近,又由于 m i d ∈ Z mid\in \mathbb{Z} midZ,故问题转化为选择 ⌊ 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 x1<xx,故选择 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+r1<mid2l+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+r1>2l+l1=l1,mid2l+r<2r+r=r,

l − 1 < m i d < r l-1<mid<r l1<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 lansr=lans=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=2S,只要证 − S ‾ − 1 = − S − 1 \overline{-S}-1=-S-1 S1=S1,

0 − ( − S ‾ − 1 ) = [ 2 n − ( − S ‾ ) ] + 1 = S + 1 0-(\overline{-S}-1)=[2^n-(\overline{-S})]+1=S+1 0(S1)=[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,mid1]

由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,mid1<rl<mid<r+1

易知 x ⩽ ⌈ x ⌉ < x + 1 x\leqslant\lceil x\rceil< x+1 xx<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+rmid<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 mid2l+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} 2xZ,则 ⌈ x 2 ⌉ = x 2 \lceil\dfrac{x}{2}\rceil=\dfrac{x}{2} 2x=2x;若 x 2 ∈ Q \dfrac{x}{2}\in \mathbb{Q} 2xQ,则 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 = midl = mid
mid不合l = mid+1r = 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=mid1:符合便向左缩小时 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 mid1混入

实数域上的二分

实现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 rl<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)时,如图:

l<r

由于 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)时,如图:

l>r

同理,利用 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=r

由于该函数在极值点两侧具有严格单调性,故 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型为例):

vlphlR.png

不难发现,二分法事实上求得的是极值点,且 + 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 n1aiavg

为了便于计算,我们将等式右边化为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 ainavg(aiavg)0

故把数列中每一个数减去二分值,并判断是否存在一个长度不小于F的连续子段,子段和非负。

部分和使用前缀和简化计算,取 a j + 1.. i ( i ⩾ j + F ) a_{j+1..i}(i\geqslant j+F) aj+1..i(ij+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+1iak=Finmax{Si0jiFminSj}

并且 min ⁡ 0 ⩽ j ⩽ i − F S j \min\limits_{0\leqslant j\leqslant i-F}S_j 0jiFminSj可随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 n3时,假设 n = k n=k n=k时结论成立,即 k k k点的竞赛图存在Hamilton路径;

n = k + 1 n=k+1 n=k+1时,有以下三种情况:

1.第k+1点有一条指向原Hamilton路径起点的出边,如图:

vlKFl6.png

那么我们直接将它作为新Hamilton路径的起点;

2.第k+1点有一条被原Hamilton路径终点指向的入边,如图:

vlKAOO.png

那么我们直接将它作为新Hamilton路径的终点;

3.若以上两种情况都不符合,即第k+1点被原Hamilton路径起点指向的入边,有指向原Hamilton路径终点的出边,如图:

vlKk6K.png

方便起见,我们将指向第k+1点的入边记作0,由第k+1点发出的出边记作1,问题转化为证明0-1串 0 … 1 0…1 01中存在相邻两数为 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 01处,仅有符合情况时,会插入至 ↓ 1 \downarrow1 1处,符合条件;而仅有不符情况时,会插入至 0 ↓ 0 0\downarrow0 00处(未对末位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位)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值