三分
比如求一段连续凸曲线的最大值,第一反应可能是求导。但其实可以用三分的方法去解。
-----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个物品是的所选的最大。
注意,并不是选择性价比最大的就最好。例如:200/99 403/201 2/1中选2个,则选前两个不如选第一个和第三个。这题可以用二分来做。通过二分查找出来的答案为x;存在一种选k个的方案,将与x进行比较,其中x若较小,则说明答案比x大,需增大下界继续二分查找;反之就降低上界二分查找。具体操作的时候,可以将式子变形,拿增大下界举例:,此式可以化为,由此可以将问题转化为:在中选最大的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;
}