二分,三分算法入门
二分:
主要是求一元方程中x的值的问题,通过从最小值到最大值二分枚举,可以降下很大的复杂度,从而实现时间的优化。
以前觉得二分不就是折半查找吗,而且限制很多(要数组有序),然后就觉得在乱序情况下排个序再二分,还不如直接贪心(不会算复杂度吃的亏),然后发现,有很多类型的题目用二分效率高。
一道典型的例题: Pie (hdu 1969)
Problem Description
My birthday is coming up and traditionally I’m serving pie. Not just one pie, no, I have a number N of them, of various tastes and of various sizes. F of my friends are coming to my party and each of them gets a piece of pie. This should be one piece of one pie, not several small pieces since that looks messy. This piece can be one whole pie though.
My friends are very annoying and if one of them gets a bigger piece than the others, they start complaining. Therefore all of them should get equally sized (but not necessarily equally shaped) pieces, even if this leads to some pie getting spoiled (which is better than spoiling the party). Of course, I want a piece of pie for myself too, and that piece should also be of the same size.
What is the largest possible piece size all of us can get? All the pies are cylindrical in shape and they all have the same height.
but the radii of the pies can be different.
Input
One line with a positive integer: the number of test cases. Then for each test case:
One line with two integers N and F with 1 <= N, F <= 10 000: the number of pies and the number of friends.
One line with N integers ri with 1 <= ri <= 10 000: the radii of the pies.
Output
For each test case, output one line with the largest possible volume V such that me and my friends can all get a pie piece of size V. The answer should be given as a floating point number with an absolute error of at most 10^(-3).
Sample Input
3
3 3
4 3 3
1 24
5
10 5
1 4 2 3 4 5 6 5 4 2
Sample Output
25.1327
3.1416
50.2655
题目大致意思:有n块饼,知道第i块饼的半径为r[i],有f+1个人,要使得这f+1个人分得相等体积的饼,但是不能两块拼凑出一块,求每个人能分得的最大体积。
思路:我第一想法是贪心,但搞了很久也没贪心出名堂来,正解是二分每个人能得到的最大体积。这里PI的精度很高,要用这个公式。
PI=acos(−1.0)
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define PI acos(-1.0)
using namespace std;
const int N = 1e4+4;
double pie[N];
int n, f;
bool cmp(double x, double y) {
return x > y;
}
//我这里用的是递归式二分,平时迭代二分用的比较多
double binary(double l, double r) {
double mid = (l + r) / 2;
int res = 0;
for(int i = 0; i < n; ++i) {
if(pie[i] < mid) break; //剪枝
else {
res += (int)(pie[i] / mid);
}
if(res >= f) break; //剪枝
}
//控制精度
if(r - l <= 0.00001) return mid;
if(res >= f) return binary(mid, r);
if(res < f) return binary(l, mid);
}
int main() {
int t; scanf("%d", &t);
while(t--) {
scanf("%d%d", &n, &f); f++;
for(int i = 0; i < n; ++i) {
scanf("%lf", &pie[i]);
pie[i] = pie[i]*pie[i]*PI;
}
sort(pie, pie+n, cmp); //先对饼排序,方便剪枝
printf("%.4lf\n", binary(0, pie[0]));
}
return 0;
}
那么二分的应用是求得一个方程中的未知数,那么三分算法是什么呢?
三分:
简明扼要的说三分主要用于求解一个单峰函数的极值。例如最典型的二次函数,运用三分法很容易求得极值。三分,顾名思义,二分是分成两部分判断中间的值是否等于要找的值,三分就是分成三部分,取中间两个端点每次比较取哪一边。每次取比较大(比较小)的那边再进行三分。
三分的题目关键在于找到那个单峰函数的方程,往往这也是比较难的,所以我觉得三分是不难的,难的是找到目标函数。
一道 入门 例题: Toxophily (hdu 2298)
Problem Description
The recreation center of WHU ACM Team has indoor billiards, Ping Pang, chess and bridge, toxophily, deluxe ballrooms KTV rooms, fishing, climbing, and so on.
We all like toxophily.
Bob is hooked on toxophily recently. Assume that Bob is at point (0,0) and he wants to shoot the fruits on a nearby tree. He can adjust the angle to fix the trajectory. Unfortunately, he always fails at that. Can you help him?
Now given the object’s coordinates, please calculate the angle between the arrow and x-axis at Bob’s point. Assume that g=9.8N/m.
Input
The input consists of several test cases. The first line of input consists of an integer T, indicating the number of test cases. Each test case is on a separated line, and it consists three floating point numbers: x, y, v. x and y indicate the coordinate of the fruit. v is the arrow’s exit speed.
Technical Specification
1、 T ≤ 100.
2、0 ≤ x, y, v ≤ 10000.
Output
For each test case, output the smallest answer rounded to six fractional digits on a separated line.
Output “-1”, if there’s no possible answer.
Please use radian as unit.
Sample Input
3
0.222018 23.901887 121.909183
39.096669 110.210922 20.270030
138.355025 2028.716904 25.079551
Sample Output
1.561582
-1
-1
题目大致意思:有一个人站在(0,0)点,他想以v的初速度射击在(x, y) 点的水果,若射不到输出-1,若射的到输出与x轴的弧度制夹角。
思路:首先…懵逼了很久,因为高中物理知识忘的差不多了,随便胡搞出一个公式:
Hmax(θ)=x∗tan(θ)−4.9∗v2x2∗(tan2(θ)+1)
然后盯着这个公式看了半天,要射中水果,在这条以 tan(θ)为自变量的一元二次方程里,我要求的是 θ,但我要怎样通过水果的位置找到 Hmax呢?这里就是三分的show time了,利用三分枚举0~90°所有的 Hmax, 找到 Hmax对应的角度thr,再用二分枚举0~thr,找能射到水果的最小的角度。有点绕,但理解了就很简单。
#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define PI acos(-1.0)
#define eps 1e-8 //精度要求很高
double x, y, v;
using namespace std;
double cal(double a) {
double t = x / (v*cos(a));
return v*sin(a)*t - 9.8/2*t*t;
}
double thrdiv() { //三分
double l = 0, r = PI / 2.0;
double mid1, mid2;
while(r - l > eps) {
mid1 = l + (r - l) / 3; mid2 = mid1 + (r - l) / 3;
if(cal(mid1) > cal(mid2)) r = mid2;
else l = mid1;
}
return l;
}
double binary(double l, double r) { //二分
double mid;
while(r - l > eps) {
mid = (l + r) / 2.0;
if(cal(mid) < y) l = mid;
else r = mid;
}
return l;
}
int main() {
int t; scanf("%d", &t);
while(t--) {
scanf("%lf%lf%lf", &x, &y, &v);
double thr = thrdiv();
if(cal(thr) < y) {
printf("-1\n"); continue;
}
if(fabs(cal(thr)-y) <= eps) {
printf("%.6lf", thr); continue;
}
printf("%.6lf\n", binary(0, thr));
}
return 0;
}