首页 > 其他分享 >【寻迹】二分与三分

【寻迹】二分与三分

时间:2024-07-07 20:09:03浏览次数:7  
标签:二分 return int double 寻迹 mid check 三分

二分与三分

二分是一种常用且非常精妙的算法。(英才计划甚至还水了一篇文章)三分法则可以用来解决单峰函数的极值以及相关问题

一、二分

二分法,在一个单调有序的集合或函数中查找一个解,每次均分为左右两部分,判断解在哪一个部分后调整上下界。每次二分都会舍弃一半区间,因此效率比较高。

假设我们有一个非降序数组 \(a\) ,现要查找 \(a\) 中一个元素 \(x\) , 搜索数组的下标范围为 \([L,R]\) ,我们每次取一个中间值 \(mid=\frac{(L+R)}{2}\) ,接下来判断 \(a_{mid}\) 与 \(x\) 的大小关系,如果 \(a_{mid}<x\) 我们要在 \([mid+1,R]\) 中进行进一步的搜索,反之,我们要在 \([L,mid]\) 中进行进一步的搜索。直到我们在某一次的搜索中搜索的范围 \([L,R]\) 中只含有一个元素。

若求解的问题的定义域为整数域,对于长度为 \(N\) 的求解区间,算法需要 \(\log_2{N}\) 次确定出分界点。

对于定义域在实数域上的问题,可以用类似的方法,判断 \(R-L\) 的精度是否达到要求,即 \(R-L\geq eps\) ,但由于实数运算的精度问题,若 \(eps\) 取得太小就会导致程序死循环,因此指定二分次数更好。如果指定二分次数 \(t\) ,对于初始区间 \(L\) ,算法结束后 \(R-L\) 的值应为 \(\dfrac{L}{2^t}\) ,根据这个值来判断是否达到精度要求。

二分算法的复杂度为 \(二分次数单次判定复杂度O(二分次数\times 单次判定复杂度)\)

二、二分法常见模型

1.二分答案

最小值最大(或最大值最小)问题被称为双最值问题。双最值问题在确定答案区间后,可以用二分法二分答案,配合其他算法验证答案是否合理。根据复杂度理论,检验一个答案是否合理比直接求解一个答案的复杂度要低。因此可以将最优化问题转化为判定问题。例如,长度为 \(n\) 的序列 \(a_i\) 最多分成 \(m\) 个连续段,求所有分法中每段和的最大值的最小值是多少。

2.二分查找

最为基础最为简单的应用,例如查找 \(x\) 的排名。

3.代替三分

对于一些单峰函数,我们可以用二分导函数的方法求解函数极值,这时通常将函数的定义域定义为整数域求解比较方便,此时 \(dx\) 可以直接取整数 \(1\) 。

三、三分

三分法适用于求解凸性函数的极值问题,二次函数就是一个典型的单峰函数。

三分法与二分法一样,它会不断缩小答案所在的求解区间。二分法缩小区间利用的原理是函数的单调性,而三分法利用的则是函数的单峰性。

设当前求解区间为 \([l,r]\) ,令 \(m_1=l+\dfrac{r-l}{3}\) , \(m_2=r-\dfrac{r-l}{3}\) ,接着我们计算这两个点的函数值 \(f(m_1)\) , \(f(m_2)\) 之后我们将两点中函数值更优的那个点称为好点,而函数值更差的称为坏点。 如果 \(m_1\) 是坏点,则下一个区间为 \([m_1,r]\) ;反之( \(m_2\) 是坏点)下一个区间为 \([l,m_2]\)

下面以求上凸单峰函数最大值为例,给出代码:

double l=0,r=1e9;
while(r-l<=1e-3)
{
	double m1=l+(r-l)/3,m2=r-(r-l)/3;
	if(f(m1)<f(m2)) l=m1;//m1是坏点
	else r=m2;
}

四、题单

T1.愤怒的牛

思路:直接二分两头牛之间的最小距离,区间为 \([1,maxx]\) ,其中 \(maxx=\max (a_i)\) ,对于每一个距离我们只需写一个 \(\operatorname{check}\) 函数判断 \(mid\) 处满足条件的牛舍是否达到 \(m\) 个,如果是则 \(l=mid\) ,否则 \(r=mid-1\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,m;
int l,r,mid;
int p,cnt;
int a[N];
int check(int x)
{
	cnt=1;p=a[1];
	for(int i=2;i<=n;i++)
		if(p+x<=a[i]) { cnt++;p=a[i]; }
	if(cnt>=m) return 1;
	return 0;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	l=1,r=a[n];
	while(l<r)
	{
		mid=(l+r+1)/2;
		if(check(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l<<endl;
	return 0;
} 

T2.Best Cow Fences

思路:首先要看到数据范围 \(n\leq 1\times10^5\) ,大概率是用二分了。进而发现可以浮点二分平均数,然后验证是否存在这样一个子段。原因是:枚举长度答案不具有单调性,但是枚举平均数的话答案就具有单调性。接下来需要考虑的是怎么写 \(\operatorname{check}\) 函数。思路是,每次 \(\operatorname{check}\) 的时候求前缀和,并将每个元素减去 \(mid\) (当前平均数为 \(mid\) ),然后只需求出最大子段和判断是否大于 \(0\) 即可。

还有一个难点就是最大子段和的求解。对于终点 \(r\) 的一个子段,其最大子段和为 \(s_r-minn\) ,其中 \(minn=\min\limits_{1\leq i\leq r-L}(s_i)\)

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100050
const double eps=1e-6;
int n,L;
double l,r,mid;
double m,s[N],a[N];
int check(double x)
{
	m=0.0;
	for(int i=1;i<=n;i++) s[i]=s[i-1]*1.0+a[i]*1.0-x*1.0;
	for(int i=L;i<=n;i++)
	{
		m=min(m,s[i-L]);
		if(s[i]-m>=0) return 1;
	}
	return 0;
}
int main()
{
	cin>>n>>L;
	for(int i=1;i<=n;i++) cin>>a[i];
	l=0.0,r=2000.0;
	while(r-l>eps)
	{
		mid=(l+r)/2.0;
		if(check(mid)) l=mid;
		else r=mid;
	}
	cout<<(int)(r*1000)<<endl;
	return 0;
}

还有就是这道题是浮点二分,细节真的巨多!

T3.曲线

思路:题目中说二次函数可能退化为一次函数,又因为 \(a>0\) 所以函数 \(S_i(x)\) 只可能先减后增或者单增,所以 \(F(x)=\max\limits_{1\leq i\leq n}(S_i(x))\) 也会有相似的性质。因此直接在定义域内浮点三分即可。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100050
const double eps=1e-8;
int T,n;
double l,r,lmid,rmid;
struct Curves{ double a,b,c; };
Curves s[N];
double Cal(double x)
{
	double maxx=-0x7f7f7f7f;
	for(int i=1;i<=n;i++) maxx=max(maxx,s[i].a*x*x+s[i].b*x+s[i].c);
	return maxx;
}
int main()
{
	cin>>T;
	while(T--)
	{
		cin>>n;
		for(int i=1;i<=n;i++) cin>>s[i].a>>s[i].b>>s[i].c;
		l=0.0,r=1000.0;
		while(r-l>eps)
		{
			lmid=l+(r-l)/3.0;
			rmid=r-(r-l)/3.0;
			if(Cal(lmid)>=Cal(rmid)) l=lmid;
			else r=rmid;
		}
		printf("%.4lf\n",Cal(r));
	}
	return 0;
}

T4.数列分段Ⅱ

思路:二分答案。需要注意的细节:如果当前答案 \(mid\) 处分段次数大于 \(m\) ,说明指定的和太小,需要在 \([mid,r]\) 中继续查找;如果当前答案 \(mid\) 处分段次数恰好等于 \(m\) ,还需要再看看有没有更小的答案,所以要在 \([l,mid]\) 中查找;如果当前答案 \(mid\) 处分段次数小于 \(m\) ,则说明指定的和太大,也需要在 \([l,mid]\) 中查找。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100050
int n,m,a[N];
int l,r,mid,sum,cnt,maxx;
int check(int x)
{
	sum=0;cnt=1;
	for(int i=1;i<=n;i++)
	{
		if(sum+a[i]<=x) sum+=a[i];
		else { sum=a[i];cnt++; }
	}
	if(cnt>m) return  1;
	return 0;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) { cin>>a[i];sum+=a[i];maxx=max(maxx,a[i]); }
	l=maxx;r=sum;
	while(l<r)
	{
		mid=(l+r)/2;
		if(check(mid)) l=mid+1;
		else r=mid;
	}
	cout<<l<<endl;
	return 0;
}

T5.扩散

思路:时间可以看作是单调的。所以想到二分时间。接下来要考虑怎么判断答案是否合理。设任意两点间曼哈顿距离为 \(x\) ,可以发现,两个点形成连通块至少需要 \(\dfrac{x}{2}\) 的时间(两个点都在扩增)。对于每一次 \(\operatorname{check}\) 用并查集维护点之间的联通关系,最后只需验证是否只有一个父节点即可。记得每次二分都要初始化并查集。

代码:

#include<bits/stdc++.h>
using namespace std;
#define N 100
struct Dots{ int x,y; };
Dots a[N];
int fa[N];
int n,l,r,mid;
inline void init() { for(int i=1;i<=n;i++) fa[i]=i; }
inline int Find(int x)
{
	if(fa[x]==x) return x;
	return fa[x]=Find(fa[x]);
}
int check(int m)
{
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			int p=Find(i),q=Find(j);
			int dist=abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y);
			if(dist<=m*2) { if(p!=q) fa[p]=q; }
		}
	}
	int cnt=0;
	for(int i=1;i<=n;i++) { if(fa[i]==i) cnt++; }
	if(cnt==1) return 1;
	return 0;
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)  { cin>>a[i].x;cin>>a[i].y; }
	l=0;r=1e9;
	while(r>l)
	{
		init();
		mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	cout<<r<<endl;
	return 0;
}

T6.灯泡

思路:更像是一道数学题。先推式子。假设人到墙的距离为 \(x\) ,影长为 \(L\) 。当 \(x\in(\dfrac{hD}{H},D]\) 时,此时影子一定在地上。可以推知 \(L=-\dfrac{h}{H-h}x+\dfrac{hD}{H-h}\) 是单减的,所以最终答案肯定不会在这个区间里。进而考虑 \(x\in[0,\dfrac{hD}{H}]\) ,此时影长等于地上的影子长度(即为 \(x\) )加上墙壁上的影子长度,设为 \(n\) 。根据初中平面几何知识可以求处 \(n=\dfrac{hD-Hx}{D-x}\) ,所以最终影子长度为 \(L=n+x=\dfrac{-x^2+(D-H)x+hD}{D-x}\) ,定义域内函数可能是单调也可能是单峰。所以选择了三分写法。

代码:

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-12;
int T;
double H,h,D;
double l,r,lmid,rmid;
double check(double x){ return ((-x*x+(D-H)*x+h*D)/(D-x)); }
int main()
{
	cin>>T;
	while(T--)
	{
		cin>>H>>h>>D;
		l=0.0,r=h*D/H;
		while(r-l>eps)
		{
			lmid=l+(r-l)/3;rmid=r-(r-l)/3;
			if(check(lmid)>=check(rmid)) r=rmid;
			else l=lmid;
		}
		printf("%.12lf\n",check(r));
	}
	return 0;
} 

T7.传送带

思路:比较自然地想到答案一定是由线段 \(AE,EF,FD\) 构成,其中 \(E\) 在 \(AB\) 上, \(F\) 在 \(CD\) 上。所以只需确定 \(E,F\) 两点即可。假设已经确定了 \(E\) ,考虑 \(F\) 的位置。我们会发现 \(EF+FD\) 是一个单峰或者单调的函数,所以三分即可。那我们怎么确定 \(E\) ?我们发现确定 \(E\) 也可以使用三分,对每一个 \(E\) 去找一个 \(F\) 最后找到能够使时间最短的 \(E,F\) 即可。

代码:

#include<bits/stdc++.h>
using namespace std;
const double eps=1e-8;
double ax,ay,bx,by,cx,cy,dx,dy,p,q,r;//题目输入 
double l1x,l1y,r1x,r1y,p1x,p1y,l1midx,l1midy,r1midx,r1midy,ans1l,ans1r;//外层三分  1均表示外层 
double l2x,l2y,r2x,r2y,p2x,p2y,l2midx,l2midy,r2midx,r2midy,ans2l,ans2r;//内层三分  2均表示内层 
double ans;
inline double dist (double x1,double y1,double x2,double y2) { return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)); }
inline double cal(double fx,double fy) { return dist(fx,fy,dx,dy)/q; }
inline double check(double ex,double ey) 
{
	l2x=cx;l2y=cy;r2x=dx;r2y=dy;
	while(dist(l2x,l2y,r2x,r2y)>eps)
	{
		p2x=(r2x-l2x)/3;p2y=(r2y-l2y)/3;
		l2midx=l2x+p2x;l2midy=l2y+p2y;
		r2midx=r2x-p2x;r2midy=r2y-p2y;
		ans2l=cal(l2midx,l2midy)+dist(ex,ey,l2midx,l2midy)/r;
		ans2r=cal(r2midx,r2midy)+dist(ex,ey,r2midx,r2midy)/r;
		if(ans2l-ans2r>eps) { l2x=l2midx;l2y=l2midy; }
		else { r2x=r2midx;r2y=r2midy; }
	}
	return (cal(l2x,l2y)+dist(ex,ey,l2x,l2y)/r);
}
int main()
{
	cin>>ax>>ay>>bx>>by;
	cin>>cx>>cy>>dx>>dy;
	cin>>p>>q>>r;
	l1x=ax;l1y=ay;r1x=bx;r1y=by;
	while(dist(l1x,l1y,r1x,r1y)>eps)
	{
		p1x=(r1x-l1x)/3;p1y=(r1y-l1y)/3;
		l1midx=l1x+p1x;l1midy=l1y+p1y;
		r1midx=r1x-p1x;r1midy=r1y-p1y;
		ans1l=check(l1midx,l1midy)+dist(ax,ay,l1midx,l1midy)/p;
		ans1r=check(r1midx,r1midy)+dist(ax,ay,r1midx,r1midy)/p;//计算左右两个分点的答案值 
		if(ans1l-ans1r>eps) { l1x=l1midx;l1y=l1midy; }
		else { r1x=r1midx;r1y=r1midy; }
	}
	ans=check(l1x,l1y)+dist(ax,ay,l1x,l1y)/p;
	printf("%.2lf\n",ans);
	return 0;
}

但是这个三分嵌套是真的难写,变量最多的一集……

标签:二分,return,int,double,寻迹,mid,check,三分
From: https://www.cnblogs.com/Cybersites/p/18288863

相关文章

  • 二分模板及其原理
    直接上代码#include<bits/stdc++.h>usingnamespacestd;//#defineintlonglong//防止越界//#defintdoublelongdouble//防止越界constintL=0,R=1e9+1;//整数二分边界//constdoubleL=0,R=1e9+1;//实数二分边界constdoubleEPS=1;......
  • Arduino 驱动红外寻迹模块
    以下是使用ArduinoUnoR3驱动红外寻迹模块的详细说明、接线图和代码示例。所需材料ArduinoUnoR3红外寻迹模块(例如TCRT5000)面包板和连接线接线步骤连接红外寻迹模块:红外寻迹模块通常有一个发射器和一个接收器。将红外寻迹模块的VCC引脚连接到ArduinoUno的5V引脚。......
  • 二分图匹配
    是么时二分图这个图的节点可以被分为两个集合,使得同一集合内没有连边。匈牙利算法例题IOI有$m$道题,HPY会$n$个算法,一共有$k$个算法可以解决IOI题。HPY会的算法太多了,所有这次IOI用了这个算法,等到下一次IOI时才会记其这个算法。<h1id="constraint......
  • 二分图匹配——从入门到入土
    基础知识形式化定义:二分图:\(G=(L,R,E)\),满足\(\forall(u,v)\inE\)都有\(u\inL,v\inR\)或\(u\inR,v\inL\)。可知“图中没有长度为奇数的环”是这个图是二分图的充分必要条件。图的匹配是一个\(E_m\subseteqE\)满足\(\nexistsi\inV(\text{当}G......
  • Day1| 704. 二分查找 &27. 移除元素
    704.二分查找题目链接:https://leetcode.cn/problems/binary-search/description/思路:切记二分查找要基于排序好的数组或者数据,否则二分查找必不能使用!!!!!!!!!双指针写最简单,一个头指针从0开始,一个尾指针从数组长度-1开始,中间指针是头+尾/2,每次比较头尾中间指针的值......
  • 代码随想录算法训练营第一天 | 704. 二分查找、27. 移除元素
    704.二分查找这个之前有写过,最重要的就是把握住要去搜索的区间的形式,包括左闭右闭以及左闭右开两种classSolution{publicintsearch(int[]nums,inttarget){intleft=0,right=nums.length;while(left<right){//左闭右开的版本,结果存储......
  • 逻辑回归求解二分类问题以及SPSS的实现
    分类问题就是给出物质的属性,判断其属于什么成分,本文将讲述逻辑回归求解二分类问题本文着重于模型的实现,对于推导只是概括性的叙述目录一、问题提出二、逻辑回归函数logistic1.线性线性概率模型2.sigmod函数3.求解方法————极大似然估计4.分类原则三、SPSS实现————以水果......
  • qoj5371 Matrix (二分图匹配)
    qoj5371Matrix二分图匹配判断无解的情况,当且仅当有\(a_{i,j}\)为负数或每一行和每一列的和不相同时无解。因为\(m\leN^2\),所以我们只需要每一次至少完成一个\(a_{i,j}\)即可。观察\(B\)矩阵的形成,实际上就是一个\(i\)行只能和一个\(j\)列匹配,跑二分图匹配即可。每......
  • 【秋招刷题打卡】Day02-二分系列之-二分查找
    Day02-二分系列之-二分查找前言给大家推荐一下咱们的陪伴打卡小屋知识星球啦,详细介绍=>笔试刷题陪伴小屋-打卡赢价值丰厚奖励<=⏰小屋将在每日上午发放打卡题目,包括:一道该算法的模版题(主要以力扣,牛客,acwing等其他OJ网站的题目作为模版)一道该算法的应用题(主要以往......
  • 二分查找
    二分查找算法思想有序的序列,每次都是以序列的中间位置的数来与待查找的关键字进行比较,每次缩小一半的查找范围,直到匹配成功。一个情景:将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于......