2025dsfz集训Day2:二分与三分
DAY2:二分与三分
特别感谢 此次课的主讲 - Kwling
二分概述
- 二分法,在一个单调有序的集合或函数中查找一个解,每次分为左右两部分,判断解在那个部分并调整上下界,直到找到目标元素,每次二分都将舍弃一般的查找空间,因此效率很高。
 
二分常见模型
- 
二分答案
最小值最大(或是最大值最小)问题,这类双最值问题常常选用二分法求解,也就是确定答案后,配合贪心,\(DP\) 等其他算法检验这个答案是否合理,将最优化问题转化为判定性问题。例如,将长度为 \(n\) 的序列 \(a_i\) 分为最多 \(m\) 个连续段,求所有分法中每段和的最大值的最小是多少? - 
二分查找
用具有单调性的布尔表达式求解分界点,比如在有序数列中求数字 \(x\) 的排名。 - 
代替三分(?存疑)
有时,对于一些单峰函数,我们可以用二分导函数的方法求解函数的极值,这时通常将函数的定义域定义为整数域求解比较方便,此时 \(d_x\) 可以直接去整数 \(1\) 。 
二分使用范围:
- 必须具备单调性或者是二段性
 
参考leetcode暑假打卡活动2019——week1中
视频链接:传送门
写二分的过程:
1、确定二分边界
2、编写二分的代码框架
3、设计一个check(性质)
4、判断一下区间如何更新
5、如果更新方式是 L = Mid , R = Mid - 1 ,那么在算mid时要加1
二分查找
- 手写二分【模版】
- 记得将无序数组变得有序!
 - 记得考虑无解的情况。
 - 
#include <bits/stdc++.h> using namespace std; int target,n; int a[1010001]; int main(Designer = 洛谷@Lwj54joy,uid=845400){ cin>>n>>target; for(int i = 1;i <= n;i++){ cin>>a[i]; } int l = 1,r = n; while(l <= r){ int mid = l+r>>1; if(a[mid] < target) l = mid+1; else r = mid-1; } if(a[l] != target) cout<<-1<<endl; else cout<<l<<endl; } 
 - 函数二分【模版】
- 直接用
lower_bound求左边界,超级短。 - 
#include <bits/stdc++.h> using namespace std; int n,target; int a[1010001]; int main(Designer = 洛谷@Lwj54joy,uid=845400){ cin>>n>>target; for(int i = 1;i <= n;i++) cin>>a[i]; if(binary_search(a+1,a+1+n,target) == true){ int answer = lower_bound(a+1,a+1+n,target)-a; cout<<answer<<endl; } } 
 - 直接用
 
二分题目解法&思路
\(T1\)
#include <bits/stdc++.h>
	using namespace std;
	
	int n;
	int target,times;
	int a[1010001]; 
	int main(Designer = 洛谷@Lwj54joy,uid=845400){
		scanf("%d%d",&n,×);
		for(int i = 1;i <= n;i++){
			scanf("%d",&a[i]);
		}
		while(times--){
			scanf("%d",&target);
			int l = 1,r = n;
			while(l <= r){
				int mid = l+r>>1;
				if(a[mid] < target) l = mid+1;
				else r = mid-1; 
			}
			if(a[l] != target) printf("-1 ");
			else printf("%d ",l);
		}
		
	}
\(T2\)
#include <bits/stdc++.h>
using namespace std;
int n,l = INT_MIN,r,m;
int a[1010001]; 
bool check(int mid){
	int tot = 0;
	int times = 1;
	for(int i = 1;i <= n;i++){
		if(tot+a[i] <= mid) tot += a[i];
		else tot = a[i],times++;
	}
		return times <= m;
	}
	
	int main(Designer = 洛谷@Lwj54joy,uid=845400){
		cin>>n>>m;
		for(int i = 1;i <= n;i++){
			cin>>a[i];
			l = max(l,a[i]);
			r += a[i];
		}
		while(l <= r){
			int mid = l+r>>1;
			if(check(mid)) r = mid-1;
			else l = mid+1;
		}
		cout<<l<<endl;
		
	}
\(T3\)
	#include <bits/stdc++.h>
	using namespace std;
	int L,n,m;
	int a[2101010];
	int ans;
	inline bool check(int mid){
		int times = 0,ip = 0;
		for(int i = 1;i <= n+1;i++){
			if(a[i]-a[ip] < mid){
				times++;
				if(times > m) return false;
			}else ip = i;
		}
		return times <= m;
	}
	int main(Designer = 洛谷@Lwj54joy,uid=845400){
		cin>>L>>n>>m;	
		a[n+1] = L;
		int l = 1,r = L;
		for(int i = 1;i <= n;i++){
			cin>>a[i];
			l = min(l,a[i]-a[i-1]);
		}
		l = min(l,L-a[n]);
		while(l <= r){
			int mid = l+r>>1;
			if(check(mid)){
				l = mid+1;
			}else r = mid-1;
		}
		cout<<l-1<<endl;
		
	}
\(T4\)
#include <bits/stdc++.h>
using namespace std;
int n,m;
int a[2101010];
int ans;
inline bool check(int mid){
	int dis = a[1],mm = 1;
	for(int i = 2;i <= n;i++){
		if(a[i]-dis >= mid){
			mm++;
			dis = a[i];
		}
	}
	return mm >= m;
}
int main(Designer = 洛谷@Lwj54joy,uid=845400){
	cin>>n>>m;
	int l = 1,r = -99;
	for(int i = 1;i <= n;i++){
		cin>>a[i];
	}
	sort(a+1,a+1+n);
	for(int i = 1;i <= n;i++){
		l = min(l,a[i]-a[i-1]);
	}
	r = a[n]-a[1];
	while(l <= r){
		int mid = l+r>>1;
		if(check(mid)){
			l = mid+1;
		}else r = mid-1;
	}
	cout<<l-1<<endl;
	
}
\(T5\)
#include <bits/stdc++.h>
using namespace std;
int a[101010],n,L;
double b[101010];
int main(Designer = 洛谷@Lwj54joy,uid=845400){
	cin>>n;
	cin>>L;
	int maxx = 0;
	for(int i = 1;i <= n;i++) cin>>a[i],maxx = max(maxx,a[i]);
	double l = 0,r = maxx*1.0,mid;
	while(r-l > 1e-5){
		mid = (l+r)/2;
		for(int i = 1;i <= n;i++) 
		b[i] = (b[i-1]+a[i]*1.0-mid)*1.0;
		double ans = 0;
		bool fl = 0;
		for(int i = L;i <= n;i++){
			ans = min(ans,b[i-L]);
			if(b[i]-ans >= 0.0){
				fl = 1;
				break;
			}
		}
		if(fl == 1) l = mid;
		else r = mid;
	}
	cout<<(int)(r*1000)<<endl;
}
\(T6\)
- 
\(F[find(i)] == k\)
其中, \(k\) 为常量, \(i\) 为 \(1-n\) 中的任意值。
那么,仔细想想就能想得到,直接二分扩散时间。因为每个点都在扩散,那么两点之间的曼哈顿距离必须小于等于扩散时间乘 \(2\) 才能保证两点在扩散中相遇。
在 \(check\) 函数中,我们直接暴力枚举每个点,如果在 \(mid\) 时间及以前能够相遇,就在并查集中合并二者。最后,如果所有的点拥有相同的公共祖先,即 \(F[find(i)] == k\) ,就可以返回 \(true\) 了。 
#include <bits/stdc++.h>
using namespace std;
struct node{
    int x,y;
}a[1010101];
int f[1010101][2];
int n;
int find(int x){
    if(f[x][0] != x) return f[x][0] = find(f[x][0]);
    return x;
}
void merge(int x,int y){
    f[find(x)][0] = f[find(y)][0];
    f[find(y)][1] += f[find(x)][1];
}
bool check(int x){
    for(int i = 1;i <= n;i++){
        for(int j = i+1;j <= n;j++){
            if(abs(a[i].x-a[j].x)+abs(a[i].y-a[j].y) <= x*2 and find(i) != find(j)) merge(i,j);
        }
    }
    int k = find(1);
    for(int i = 2;i <= n;i++){
        if(find(i) != k) return false;
    }
    return true;
}
int main(Designer = 洛谷@Lwj54joy,uid=845400){
    cin>>n;
    for(int i = 1;i <= n;i++){
        cin>>a[i].x>>a[i].y;
    }
    int l = 0,r = 1e9;
    while(l <= r){
        for(int i = 1;i <= n;i++) f[i][0] = i,f[i][1] = 1;
        int mid = l+r>>1;
        if(check(mid)) r = mid-1;
        else l = mid+1;
    }
    cout<<l<<endl;
}
三分
- 
三分算法(Ternary Search Algorithm)是一种用于在单峰函数中寻找极值的优化算法。它通过将搜索区间分为三个部分并比较函数在两个划分点的取值,逐步缩小搜索范围来确定极值的位置。(求解带曲线的多次函数)
 - 
三分算法的基本思路如下:
- 
初始化搜索区间,通常是整个定义域的区间。
 - 
将搜索区间分为三个部分,通常采用两个划分点将整个区间分成三等份。
 - 
计算两个划分点的函数取值。
 - 
比较两个划分点的函数取值,如果其中一个划分点的函数值更大(或更小),则极值在该划分点的同侧。
 - 
根据比较结果更新搜索区间,将较小(或较大)的划分点所在的区间作为新的搜索区间。
 - 
重复步骤3到步骤5,直到搜索区间足够小,或达到一定的迭代次数。
 - 
返回搜索区间的中点作为极值的近似位置。
 
 - 
 
三分算法的时间复杂度为 \(O(logN)\) ,其中 \(N\) 为搜索区间的长度。它的优势在于可以在单峰函数中快速找到极值的位置,但不适用于非单调函数或多峰函数的寻优问题。
三分题目解法&思路
\(T1\)
- 
随便画出二次函数的一段(二次函数是一个特殊的下凸函数),
我们发现一个下凸函数其上四个点无非有以下几种情况:设下凸函数上四个点横坐标分别为 \(x1<x2<x3<x4\) ,
并且我们已经确定该函数的最小值在 \([x1,x4]\) 之间若F(x1)<F(x2),则函数的最小值的x的取值范围一定在[x1,x2]之间,因为根据定义,
若有 \(F(x1)<F(x2)\) ,必有 \(F(x1)<F(x2)<F(x3)<F(x4)\) ;若 \(F(x3)>F(x4)\) ,同理可得,函数最小值的取值范围一定是在 \([x3,x4]\) 之间。
若 \(F(x1)>F(x2)\ \&\ F(x2)<F(x3)\) ,
则函数的最小值的 \(x\) 的取值范围一定在 \([x1,x3]\) 之间,
因为下凸函数有且仅有一个低谷,并且下凸函数的最小值在低谷中。若 \(F(x4)>F(x3)\ \&\ F(x3)<F(x2)\),
同理,函数最小值的取值范围一定在 \([x2,x4]\) 之间。最后就只剩下一种情况了,此时函数最小值的取值范围一定在 \([x2,x3]\) 之间。
 
#include <bits/stdc++.h>
	using namespace std;
	int n;
	double a[101010],b[101010],c[101010];
	double f(double x){
		double mx = -1e9;
		for(int i = 1;i <= n;i++) mx = max(mx,a[i]*x*x+b[i]*x+c[i]);
		return mx;
	}
	int main(Designer = 洛谷@Lwj54joy,uid=845400){
		int T;
		cin>>T;
		while(T--){
			scanf("%d",&n);
			for(int i = 1;i <= n;i++) scanf("%lf%lf%lf",&a[i],&b[i],&c[i]);
			double l = 0,r = 1000;
			while(r-l>1e-11){
				double m1 = l+(r-l)/3,m2 = r-(r-l)/3;
				if(f(m1) <= f(m2)) r = m2;
				else l = m1;
			}
			printf("%.4lf\n",f(r));
		}
	}
\(T2\)
#include <bits/stdc++.h>
	using namespace std;
	int n;
	double a[101010],b[101010],c[101010];
	double f(double x){
		double mx = -1e9;
		for(int i = 1;i <= n;i++) mx = max(mx,a[i]*x*x+b[i]*x+c[i]);
		return mx;
	}
	int main(Designer = 洛谷@Lwj54joy,uid=845400){
		int T;
		cin>>T;
		while(T--){
			scanf("%d",&n);
			for(int i = 1;i <= n;i++) scanf("%lf%lf%lf",&a[i],&b[i],&c[i]);
			double l = 0,r = 1000;
			while(r-l>1e-11){
				double m1 = l+(r-l)/3,m2 = r-(r-l)/3;
				if(f(m1) <= f(m2)) r = m2;
				else l = m1;
			}
			printf("%.4lf\n",f(r));
		}
	}
\(T3\)
	#include <bits/stdc++.h>
	using namespace std;
	int t;
	double hb,h,d;
	double f(double x){
		double l = hb-(d*(hb-h)/x);
		return d-x+l;
	}
	int main(Designer = 洛谷@Lwj54joy,uid=845400){
		cin>>t;
		while(t--){
			cin>>hb>>h>>d;
			double l = d-(h*d/hb),r = d;
			while(r-l >= 1e-5){
				double m1 = l+(r-l)/3,m2 = r-(r-l)/3;
				if(f(m1) < f(m2)) l = m1;
				else r = m2;
			}
			printf("%.3lf\n",f(r));
		}	
	}

                
            
        
浙公网安备 33010602011771号