二分、三分、01规划3

本文介绍了如何利用三分法和二分法解决数学中的凸函数最大值问题。通过实例展示了在连续凸曲线上找到最大值的过程,以及如何避免在精度问题上遇到死循环。此外,还探讨了这两种方法在最小圆覆盖问题和资源分配问题中的应用。文章强调在实际编程中考虑数据范围和精度的重要性,并提供了具体的代码实现。
摘要由CSDN通过智能技术生成

三分

比如求一段连续凸曲线的最大值,第一反应可能是求导。但其实可以用三分的方法去解。

-----left-----mid1-----mid2-----right----- 请将左侧例子自动想成一段连续凸曲线上的5段,段与段之间相隔的为我们设的点。若mid1的值大于mid2的值,则说明波峰不可能出现在right的右侧,此时舍去这一段。另外,如果连续可导也可以先求导再通过二分寻找答案。

二分、三分中的精度问题

有的题目要求我们使用double求解答案,一般我们会将结束循环的条件设置为一个很小的数,但这种做法有时也会出错,因为除以2带来的误差导致死循环。防止死循环的方法为:直接固定二分次数,1000次即可。

例1

 类似二分的定义Left和Right,mid = (Left + Right) / 2,midmid = (mid + Right) / 2;

如果mid靠近极值点,则Right = midmid;否则(即midmid靠近极值点),则Left = mid;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const double eps=1e-8;
double H,h,D,ans;
double Cal(double l) {
	return l+D*(h-l)/(H-l);
}
int main() {
	int t;
	scanf("%d",&t);
	while(t--) {
		scanf("%lf%lf%lf",&H,&h,&D);
		double left=0,right=h,mid,midmid;
		while(right-left>=eps) {
			mid=(left+right)/2;
			midmid=(mid+right)/2;
			if(Cal(mid)>Cal(midmid))
				right=midmid;
			else
				left=mid;
		}
		printf("%.3lf\n",Cal(left));
	}
	return 0;
}

最小圆覆盖

给定n个点,用一个最小的圆把这些点全部覆盖,求这个圆的圆心半径。

可以证明,无论如何变动x、y轴坐标,随着圆心在x轴或y轴由最左移至最右的过程中,半径是先变小后变大的,相当于一个三维的凹面,因此可以用三分套三分来求解最小半径,即可先写外层三分求解x,后在当前求出的基础上求解最小的y。

思路同上一题,假设已知应从AB中的x点脱离传送带,则从什么地方进CD的y点就可以用三分,也是三分套三分。

01分数规划:2976 -- Dropping tests

有一堆物品,每一个物品有一个收益ai,一个代价bi,我们要求一个方案选k个物品是的所选的\frac{\sum a_{i}}{\sum b_{i}}最大。

注意,并不是选择性价比最大的就最好。例如:200/99 403/201 2/1中选2个,则选前两个不如选第一个和第三个。这题可以用二分来做。通过二分查找出来的答案为x;存在一种选k个的方案,将\frac{\sum a_{i}}{\sum b_{i}}与x进行比较,其中x若较小,则说明答案比x大,需增大下界继续二分查找;反之就降低上界二分查找。具体操作的时候,可以将式子变形,拿增大下界举例:\frac{\sum a_i}{\sum b_i}> x,此式可以化为\sum \left(a_i-xb_i \right )>0,由此可以将问题转化为:在\left(a_i-xb_i \right )中选最大的k个,大于0则增大下界,否则降低上界。

细节方面,不开long long见祖宗!一定要预判数据范围啊啊啊!!!

做题经验:满足答案的一方(如if(jg()) l=mid+1)最终答案即为l-1

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=10005,inf=0x7fffffff;
const double esp=1e-8;
int t,n,k;
long long c[maxn],v[maxn],d[maxn],mid;
int main(){
	cin>>t;
	while(t--){
		scanf("%d%d",&n,&k);
		for(int i=1;i<=n;++i)
			scanf("%d%d",&c[i],&v[i]);
		int l=0,r=inf,cnt=0;
		while(l<=r){
			mid=(l+r)>>1;
			long long ans=0;
			for(int i=1;i<=n;++i)
				d[i]=v[i]-mid*c[i];
			sort(d+1,d+1+n);
			for(int i=n;i>n-k;--i)
				ans+=d[i];
			if(ans>=0) l=mid+1;
			else r=mid-1;
		}
		cout<<l-1<<endl;
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值