整数集合上的二分
在单调递增序列a中查找 ≥ x \geq x ≥x的数中最小的一个。
while(l<r){
int mid=(l+r)>>1;
if(a[mid]>=x)r=mid;
else l=mid+1;
}
return a[l];
在单调递增序列a中查找 ≤ x \leq x ≤x的数中最大的一个。
while(l<r){
int mid=(l+r+1)>>1;
if(a[mid]>=x)l=mid;
else r=mid-1;
}
return a[l];
对于后者为什么是(l+r+1)>>1?如果也采用(l+r)>>1,那么r-l=1时,就有mid=l,如果进入l=mid分支,会造成死循环,如果进入r=mid-1分支,造成l>r,循环不能以l=r结束。
mid=(l+r)>>1不会取到r,mid=(l+r+1)>>1不会取到l。可以利用这个性质处理无解的情况,把最初的二分区间[1,n]分别扩大到[1,n+1],[0,n]。这样如果最后停在了这个下标,则说明不存在。
实数域上的二分
确定好精度eps以l+eps<r为循环条件,每次根据mid上的判定选择r=mid或l=mid分支之一。需要保留k位小数时,取 e p s = 1 0 − ( k + 2 ) 。 eps=10^{-(k+2)}。 eps=10−(k+2)。
while(l+1e-5<r)
{
double mid=(l+r)/2;
if(calc(mid))r=mid;
else l=mid;
}
固定次数的方法:
for(int i=0;i<100;i++)
{
double mid=(l+r)/2;
if(calc(mid))r=mid;
else l=mid;
}
三分求单峰函数极值
单峰函数:有唯一的极大值点,左侧严格单调递增,右侧严格单调递减。
单谷函数:有唯一的极小值点,左侧严格单调递减,右侧严格单调递增。
以单峰函数f为例,在定义域
[
l
,
r
]
[l,r]
[l,r]上任取两点
l
m
i
d
,
r
m
i
d
lmid,rmid
lmid,rmid,把函数分成三段。:
1.若
f
(
l
m
i
d
)
<
f
(
r
m
i
d
)
,
可令
l
=
l
m
i
d
f(lmid)<f(rmid),可令l=lmid
f(lmid)<f(rmid),可令l=lmid。
2.若
f
(
l
m
i
d
)
>
f
(
r
m
i
d
)
,
可令
r
=
r
m
i
d
f(lmid)>f(rmid),可令r=rmid
f(lmid)>f(rmid),可令r=rmid。
二分答案转化为判定
二分答案的本质是建立一个定义域为解空间、值域为0或1的单调分段0/1函数,在这个函数上二分查找分界点。
例题
acwing102.最佳牛围栏
前置问题:
1.求一个子段,它的和最大,没有长度不小于L的限制。
O
(
n
)
O(n)
O(n)扫描该数列,不断把新的数加入子段,当子段变成负数时,把当前整个子段清空。扫描过程中出现的最大值为所求。
2.求一个字段,它的和最大,字段长度不小于L。
转化为前缀和相减的问题。
max
i
−
j
≥
L
{
A
j
+
1
+
A
j
+
2
+
.
.
.
+
A
i
}
=
max
L
≤
i
≤
n
{
s
u
m
i
−
min
0
≤
j
≤
i
−
L
s
u
m
j
}
\max\limits_{i-j\geq L}{\{A_{j+1}+A_{j+2}+...+A_{i}\}=\max\limits_{L\leq i\leq n}\{{sum_i-\min\limits_{0\leq j\leq i-L}{sum_j}}\}}
i−j≥Lmax{Aj+1+Aj+2+...+Ai}=L≤i≤nmax{sumi−0≤j≤i−Lminsumj}
#include<iostream>
using namespace std;
#define MAX_N 100000
double a[MAX_N+5],b[MAX_N+5],sum[MAX_N+5];
int n,f;
bool check(double x)
{
for(int i=1;i<=n;i++)
b[i]=a[i]-x;
for(int i=1;i<=n;i++)
sum[i]=sum[i-1]+b[i];
double minn=2000;
double ans=-2000;
for(int i=f;i<=n;i++)
{
minn=min(minn,sum[i-f]);
ans=max(ans,sum[i]-minn);
if(ans>=0)return 1;
}
return 0;
}
int main()
{
cin>>n>>f;
for(int i=1;i<=n;i++)
cin>>a[i];
double l=0,r=2000,mid;
while(r-l>1e-5)
{
mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
cout<<int(r*1000)<<endl;
return 0;
}