二分 & 分治
二分
二分查找
也叫折半搜索,用于在一个有序数组中查找某一元素的算法。
给定一个长度为 n n n 的从小到大排列的数列 a a a 和 q q q 次询问,每次询问给定 x x x,查找 x x x 在数列中的位置。
n , q ≤ 1 0 6 n,q\le 10^6 n,q≤106。
对于 a i a_i ai,如果 x < a i x<a_i x<ai,因为 a a a 有序,则 x < a [ i , n ] x<a_{[i,n]} x<a[i,n];同理如果 x > a i x>a_i x>ai,则 x > a [ 1 , i ] x>a_{[1,i]} x>a[1,i]。然后就可以二分了。令 [ l , r ] [l,r] [l,r] 表示 x x x 可能的区间,每轮求出一个 m i d = l + r 2 mid=\cfrac{l+r}{2} mid=2l+r,如果 x = a m i d x=a_{mid} x=amid 则 m i d mid mid 即为答案;如果 x < a m i d x<a_{mid} x<amid 则区间被缩短为 [ l , m i d − 1 ] [l,mid - 1] [l,mid−1];否则区间被缩短为 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。每一轮使答案区间减半,所以单词查询复杂度为 O ( log n ) O(\log n) O(logn)。
int Query(int x) {
int l = 1, r = n, mid
while (l <= r) {
mid = l + r >> 1; // >> 1 与 / 2 基本等价
if (a[mid] == x) return mid;
if (x < a[mid]) r = mid - 1;
else l = mid + 1;
}
return -1;
}
可以直接使用 STL 中的两个二分查找函数:
upper_bound(a + 1,a + n + 1,x); // 返回数组中第一个大于 x 的数的地址,如果需要下标则要减去 a
lower_bound(a + 1,a + n + 1,x); // 返回数组中第一个不小于 x 的数的地址
// 如果没有结果则返回 end()
要求传入的数组 a a a 有序。
共有两个人,规定一场比赛共有 s s s 局,每一局中先得到 t t t 分的人获得这一局的胜利。现在按照时间给定这两人的得分情况,但不知道是第几局得到的第几分,只知道是谁得到的分。求所有可能的 ( s , t ) (s,t) (s,t),保证得分情况是完整的。
当其中一人得到 t t t 分时,一局就会结束,所以一局中两人一共最多得 2 t − 1 2t-1 2t−1 分,最少得 t t t 分。所以考虑前缀和,预处理出两个人在任意时间段分别得的分,然后若干轮二分将得分情况切分成若干局。假设上一轮二分将这一局的得分情况起点推到了 L L L,则本轮二分的答案区间即为 [ L + t , L + 2 t − 1 ] [L+t,L+2t-1] [L+t,L+2t−1];对于二分中得到的某个 m i d mid mid:
- 如果在 [ L , m i d ] [L,mid] [L,mid] 中,其中一人得到了 t t t 分,另一个得到了 t t t 分一下,则 m i d mid mid 即为本局终点;
- 如果在 [ L , m i d ] [L,mid] [L,mid] 中,其中一个得到超过 t t t 分或两人都得到了 t t t 分,说明 m i d mid mid 太靠后了,二分区间变更为 [ l , m i d − 1 ] [l,mid-1] [l,mid−1];
- 否则说明 m i d mid mid 太靠前了,二分区间变更为 [ m i d + 1 , r ] [mid+1,r] [mid+1,r]。
最终判断是否所有得分情况都被一局包含即可。复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n), n n n 为得分情况总数。
二分答案
针对于答案具有单调性的题目。即如果答案为
x
x
x 成立,那么比
x
x
x 更宽松的也成立;如果
x
x
x 不合法,那么比
x
x
x 更苛刻的也不合法。所以考虑枚举答案判断是否成立,类似于二分查找,也有一个答案区间
[
l
,
r
]
[l,r]
[l,r],判断
m
i
d
mid
mid 是否合法时需要一个 check
函数。意义即为:花费一个
log
n
\log n
logn 的时间复杂度,将求答案转换为判断答案是否合法。如果题目中的答案明显具有单调性,或者要求最大值最小/最小值最大。check
函数通常和贪心算法或一些数据结构配合使用。
P2678 [NOIP2015 提高组] 跳石头
题目中 最短跳跃距离尽可能长
提示使用二分答案,即二分这个最短跳跃距离。对于二分值
x
x
x,check
中枚举相邻石头,如果发现两者距离小于
x
x
x,则说明需要移走一块石头。判断移走石头的数量是否小于
M
M
M 即可。细节部分不作赘述。
有一条数轴和 n n n 个人,第 i i i 个人的移动速度为 v i v_i vi,位置为 x i x_i xi。现在要求选定一个集合点,使得最后一个到这个点的人所花费时间最小。求这个最小时间。
也是二分这个时间。对于时间 t t t,第 i i i 个人的活动范围即为 [ x i − v i × t , x i + v i × t ] [x_i-v_i\times t,x_i+v_i\times t] [xi−vi×t,xi+vi×t]。最后看 n n n 个人的活动范围是否存在交集即可。可以开 L , R L,R L,R 两个变量表示当前交集的左右端点, O ( n ) O(n) O(n) 扫一遍更新,最后看 L ≤ R L\le R L≤R 是否成立即可。
P4409 [ZJOI2006] 皇帝的烦恼
50 % 50\% 50% 左右做法:求 max { a i + a i + 1 } \max\{a_i+a_{i+1}\} max{ai+ai+1} 即可,只适用于 n n n 为偶数的情况。其实加一小句话,这个做法也能 AC。
100
%
100\%
100% 做法:二分勋章数,令
f
i
f_i
fi 表示第
i
i
i 个人在不与
i
−
1
i-1
i−1 冲突的情况下,
i
i
i 与
1
1
1 的最小冲突量;
g
i
g_i
gi 表示不与
i
−
1
i-1
i−1 冲突时与
1
1
1 的最大冲突量。假设二分到勋章数为
m
m
m,则转移方程即为:
f
i
=
min
(
a
i
,
a
1
−
g
i
−
1
)
,
g
i
=
max
(
0
,
a
i
−
(
m
−
a
1
−
a
a
−
1
)
−
f
i
−
1
)
f_i=\min(a_i,a_1-g_{i-1}),g_i=\max(0,a_i-(m-a_1-a_{a-1})-f_{i-1})
fi=min(ai,a1−gi−1),gi=max(0,ai−(m−a1−aa−1)−fi−1)
可以对着方程推一下,
m
m
m 合法的条件即为
g
n
=
0
g_n=0
gn=0。
分治
顾名思义,将问题分成几个子问题,然后通过子问题的解合并出该问题的解。而对于长度为 1 1 1 等规模很小的子问题则直接解决。通常使用递归解决。
归并排序, merge_sort \texttt{merge\_sort} merge_sort
对于分治区间 [ L , R ] [L,R] [L,R],使得 [ L , R ] [L,R] [L,R] 中的元素有序。如果 L = R L=R L=R 则直接返回,否则将当前序列平分成左右两半分治解决,然后将左右两个有序合并。具体地,不断从两个有序序列首部取较小值放入答案序列即可。
快速排序, quick_sort \texttt{quick\_sort} quick_sort
区别在于每次从 [ L , R ] [L,R] [L,R] 中随机选出一个元素作为基准,将比它小的元素排到左边,比它大的排到右边,然后再递归处理左右序列。最坏情况是 O ( n 2 ) O(n^2) O(n2) 的。
P7883 平面最近点对(加强加强版)
以 x x x 坐标排序,考虑按 x x x 坐标分治。每次取中间的点将剩余点集分为规模相等的两部分,递归求出左右两侧的点对最近距离,记为 a n s L , a n s R ansL,ansR ansL,ansR。则当前区间 [ L , R ] [L,R] [L,R] 中的答案 a n s ans ans 一定不大于 min ( a n s L , a n s R ) \min(ansL,ansR) min(ansL,ansR)。将 min ( a n s L , a n s R ) \min(ansL,ansR) min(ansL,ansR) 距离内的点按照 y y y 坐标排序(可以在分治过程中归并排序),对于每个左侧点,只需要考虑右侧宽 min ( a n s L , a n s R ) \min(ansL,ansR) min(ansL,ansR)、高 2 × min ( a n s L , a n s R ) 2\times\min(ansL,ansR) 2×min(ansL,ansR) 的矩形范围内的点,与其进行配对。
时间复杂度接近 O ( n log n ) O(n\log n) O(nlogn)。
杂项
浮点数二分
for (int st = 0;st < 100;st ++) { // 控制二分次数
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
} // 最终答案为 r
三分
用于求单峰函数的极值点,通常使用二分法衍生出的三分法。对于这个函数图像:
基本逻辑类似,假设 x x x 的函数值为 f ( x ) f(x) f(x),对于当前区间 [ L , R ] [L,R] [L,R](红点之间的部分),我们在区间内任取两点 m L , m R ( m L < m R ) mL,mR\ (mL<mR) mL,mR (mL<mR)(两个蓝色的点);如果 f ( m L ) < f ( m R ) f(mL)<f(mR) f(mL)<f(mR),则在 [ m R , R ] [mR,R] [mR,R](红色部分)中函数值必然单调递增,最值(绿点)必然不在这一区间。每一轮都会舍去两侧区间中的其中一个。为减少三分的次数,可以让 m L ← m i d − eps , m R ← m i d + eps mL\gets mid-\text{eps},mR\gets mid+\text{eps} mL←mid−eps,mR←mid+eps,其中 eps \text{eps} eps 为一个很小的值。复杂度和 log 2 n \log_2n log2n 十分接近。
while (r - l > eps) {
mid = (lmid + rmid) / 2;
lmid = mid - eps, rmid = mid + eps;
if (f(lmid) < f(rmid)) r = mid;
else l = mid;
}
分数规划
用来求一个形如
∑
i
=
1
n
a
i
×
w
i
∑
i
=
1
n
b
i
×
w
i
\cfrac{\sum^n_{i=1}a_i\times w_i}{\sum^n_{i=1}b_i\times w_i}
∑i=1nbi×wi∑i=1nai×wi
的分式的极值,其中
w
i
∈
{
0
,
1
}
w_i\in\{0,1\}
wi∈{0,1} 且会受到约束(比如说最多
n
−
1
n-1
n−1 个
w
i
w_i
wi 能取
1
1
1 等)。考虑二分这个极值
m
i
d
mid
mid,即使得
∑
i
=
1
n
a
i
×
w
i
∑
i
=
1
n
b
i
×
w
i
>
m
i
d
\cfrac{\sum^n_{i=1}a_i\times w_i}{\sum^n_{i=1}b_i\times w_i}>mid
∑i=1nbi×wi∑i=1nai×wi>mid
变换一下可得
∑
i
=
1
n
w
i
×
(
a
i
−
m
i
d
×
b
i
)
>
0
\sum^n_{i=1}w_i\times(a_i-mid\times b_i)>0
i=1∑nwi×(ai−mid×bi)>0
所以 check
部分需要使得值为正。具体怎么选取
w
i
w_i
wi 依据题目而定。
给定一张图,第 i i i 条边的边权为 w i w_i wi,求在原图中找一个环使得环上的边边权之和,与环的大小的比值最小。
根据得到的式子,把 a i − m i d × b i a_i-mid\times b_i ai−mid×bi 作为每条边的新边权,然后看图中是否存在负环即可。
下午题
Desert King
每条边有两个权值 a i a_i ai 和 b i b_i bi,求一棵生成树 T T T 使得树上边权 a a a 之和与 b b b 之和的比值最小。
类似的,把
a
i
−
m
i
d
×
b
i
a_i-mid\times b_i
ai−mid×bi 作为每条边的新边权,然后检查最小生成树是否大于
0
0
0 即可。注意这题时限卡的很紧,且没有 bits/stdc++.h
这个库可以用。
Rest In The Shades
考虑连接点 P P P 和 A , B A,B A,B,交 x x x 轴(即栅栏所在的直线)于两点 A ′ , B ′ A',B' A′,B′。算出 A ′ , B ′ A',B' A′,B′ 中栅栏的占比,此时 A , B A,B A,B 中产生阴影的距离就可以通过相似三角形求出。可以两遍二分求出 A ′ , B ′ A',B' A′,B′ 附近的线段,最后前缀和统计阴影总长度即可。
The Hidden Pair (Hard Version)
询问次数不能超过 11 11 11 次,这个数字与 log n \log n logn 的值很接近。考虑先对所有 n n n 个点进行询问,这样就可以得到 X , Y X,Y X,Y 之间的距离,和一个必定在 X , Y X,Y X,Y 路径上的点。设这个点为 r t rt rt, X , Y X,Y X,Y 之间的距离为 d i s dis dis。如果我们以 r t rt rt 为树根,那么对于树中深度为 d e p dep dep 的点集 { A } \{A\} {A},且 d e p dep dep 大于等于 X , Y X,Y X,Y 中较小的深度,我们对其进行询问:
- 如果询问得到的距离和为 d i s dis dis,则表示询问得到的点在 X , Y X,Y X,Y 的路径上,即 X , Y X,Y X,Y 中较大的深度大于等于 d e p dep dep;
- 如果询问得到的距离和大于 d i s dis dis,则表示该深度的点中没有在 X , Y X,Y X,Y 的路径上的,即 X , Y X,Y X,Y 中较大深度小于 d e p dep dep。
对于第一种情况询问到的点都可能是 X , Y X,Y X,Y 中深度较大的点,不妨令 d e p X ≥ d e p Y dep_X\ge dep_Y depX≥depY。考虑二分,区间为 [ ⌊ d i s 2 ⌋ , min ( d i s , D e p ) ] [\lfloor\cfrac{dis}{2}\rfloor,\min(dis,Dep)] [⌊2dis⌋,min(dis,Dep)],其中 D e p Dep Dep 为最大深度。每一轮都询问一次深度为 m i d mid mid 的点集,按照上述两种情况二分,最终可以得到深度较大的 X X X。然后我们以 X X X 为树根,然后询问深度为 d i s dis dis 的点集,得到的点即为 Y Y Y。