《算法竞赛进阶指南》0x04二分

整数集合上的二分

在单调递增序列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 lmidrmid,把函数分成三段。:
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}}\}} ijLmax{Aj+1+Aj+2+...+Ai}=Linmax{sumi0jiLminsumj}

#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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值