思想: 分治。
适用范围:二分只适用于单调函数,对单调递增或单调递减的一个序列中的某一个元素进行查找;三分用于凸函数和凹函数。
复杂度分析:二分的时间复杂度为log2(n),而三分的时间复杂度为3log3(n)。
题意:给出n条绳子,长度分别为Li,裁剪出m条等长且尽量长的线段,并且让这些线段尽可能长。
#include
#include
using namespace std;
#define N 11000
#define INF 1000000
double a[N];
int n, k;
double C(double x)
{
int num = 0;
for (int i = 0; i < n; i++) num += (int) (a[i] / x);
return num >= k;
}
void solve()
{
double l = 0;
double r = INF;
for (int i = 1; i < 100; i++){
double mid = (l+r)*1.0 / 2;
if (C(mid)) l = mid;
else r = mid;
}
printf("%.2f\n",floor(l*100)/100);
}
int main ()
{
//yyy_3y
freopen("1.in","r",stdin);
scanf("%d %d",&n,&k);
for (int i = 0; i < n; i++) scanf("%lf",&a[i]);
solve();
}
题意:有n个牛舍,第i个牛舍在xi的位置上面,但是m头牛会互相攻击,因此要使得最近的两头牛之间的距离尽量大,求这个距离。
思路: 1:对牛舍的位置x进行排序
2:把第一头牛放入X0 的牛舍
3.如果第i头牛放入了xj的话,第i+1头牛就要放入满足
xj ≤ ≤ xk 的最小xk中。
#include
#include
#include
using namespace std;
#define N 110000
#define INF 1000000010
int a[N];
int n, c;
bool C(int d)
{
int last = 0;
for (int i = 1; i < c; i++){
int crt = last + 1;
while (crt < n && a[crt] - a[last] < d) crt++;
// printf("i = %d ,cnt = %d\n",i,crt);
if (crt == n) return false;
last = crt;
}
return true;
}
void solve()
{
sort(a,a+n);
int l = 0;
int r = INF;
while(r - l > 1){
int mid = (l+r) / 2;
if (C(mid)) l = mid;
else r = mid;
}
printf("%d\n",l);
}
int main ()
{
//yyy_3y
// freopen("1.in","r",stdin);
scanf("%d %d",&n,&c);
for (int i = 0; i < n; i++) scanf("%d",&a[i]);
solve();
return 0;
}
题意:一共有N场考试,每场一共有b题,每场对a题,我们可以去掉k场的成绩,使得最后的正确率最大。
思路:二分正确率。
#include
#include
#include
#include
using namespace std;
#define eps 1e-6
const int N = 1010;
int n,k;
double x;
struct node{
int a,b;
bool operator <(const node & c) const{
return a-x*b < c.a-x*c.b;
}
}T[N];
bool C(double mid)
{
x=mid;
sort(T+1,T+1+n);
double a=0.0,b=0.0;
for(int i=k+1;i<=n;i++){
a+=T[i].a;
b+=T[i].b;
}
return 1.0*a/b>mid;
}
void solve()
{
double l=0.0,r=1.0;
while(r-l>=eps){
double mid=(l+r)/2;
if(C(mid)) l=mid;
else r=mid;
}
printf("%.0f\n", 100.0*l);
}
int main ()
{
//yyy_3y
// freopen("1.in","r",stdin);
while(scanf("%d%d",&n,&k)&&n+k){
for (int i = 1; i <= n; i++) scanf("%d",&T[i].a);
for (int i = 1; i <= n; i++) scanf("%d",&T[i].b);
solve();
}
}
概念:在二分查找的基础上,在右区间(或左区间)再进行一次二分,这样的查找算法称为三分查找,也就是三分法。主要来确定最值!
题意:给出一个圆锥的表面积, 求该圆锥的最大体积,以及此时的底面半径和高。
思路:数学公式推导,找到半径的上界和下届。三分半径即可。
#include
#include
#include
#include
#define PI acos(-1.0)
#define eps 1e-6
using namespace std;
double s;
double C(double r)
{
double R=(s/PI/r-r);
double h=sqrt(R*R-r*r);
return PI*h*r*r/3;
}
double solve(double left, double right)
{
double midl, midr;
while (right-left > eps)
{
midl = (2.0*left + right) / 3;
midr = (left + 2.0*right) / 3;
if(C(midl) >= C(midr)) right = midr;
else left = midl;
}
return left;
}
int main()
{
while(scanf("%lf",&s)!=EOF){
double left=0.0,right=sqrt(s/2/PI);
left=solve(left,right);
double R=(s/PI/left-left);
double h=sqrt(R*R-left*left);
double v=PI*h*left*left/3;
printf("%.2f\n%.2f\n%.2f\n", v, h, left);
}
return 0;
}
题意:有两条传送带,传送带上有两段,AB和CD,它们的速度分别是P和Q,其它地方的速度为R,问从A到D所需要的最短时间是多少。
思路:先三分AB上的点,再三分CD上的点即可。
设X在AB上,Y在CD上。
人在线段AB上花的时间为:f(x) = AX / P,
人走完Z和Y所花的时间为:g(x) = XY / R + YD / Q。
f(x)+g(x)很明显是一个先递减后递增的函数。故可用三分法。
#include
#include
#include
#include
#define PI acos(-1.0)
#define eps 1e-6
using namespace std;
struct node{
double x,y;
}a,b,c,d,X,Y;
double p,q,r;
double dis(node a,node b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
double calc(double mid)
{
Y.x=c.x+(d.x-c.x)*mid;
Y.y=c.y+(d.y-c.y)*mid;
return dis(Y,d)/q + dis(X,Y)/r;
}
double C(double mid)
{
X.x=a.x+(b.x-a.x)*mid;
X.y=a.y+(b.y-a.y)*mid;
double l=0.0,r=1.0,midl,midr;
double ans;
while(r-l>eps){
midl=(2.0*l+r)/3;
midr=(l+2.0*r)/3;
ans=calc(midl);
if(ans<=calc(midr)) r=midr;
else l=midl;
}
return dis(a,X)/p+ans;
}
void solve()
{
double l=0.0,r=1.0,midl,midr;
double ans;
while(r-l>eps){
midl=(2.0*l+r)/3;
midr=(l+2.0*r)/3;
ans=C(midl);
if(ans<=C(midr)) r=midr;
else l=midl;
}
printf("%.2f\n",C(l));
}
int main ()
{
int t; scanf("%d",&t);
while(t--){
scanf("%lf%lf%lf%lf",&a.x,&a.y,&b.x,&b.y);
scanf("%lf%lf%lf%lf",&c.x,&c.y,&d.x,&d.y);
scanf("%lf%lf%lf",&p,&q,&r);
solve();
}
}
想到总结三分也是应为这一道题。这道题可以拿尺取法解—->尺取法
但是三分也是一种非常优秀的思路。
题意:
有两种操作。
1.往集合里面加一个数(这个数为集合中最大的数)。
2.在该集合取任一子集使得Max子集(子集中最大的元素)-AVG子集 最大
#include
#define debug(a) cout << #a << " " << a << endl
#define LL long long
#define PI acos(-1.0)
#define eps 1e-6
const int N=1e6+7;
using namespace std;
LL sum[N],x;
int cnt=0;
double ans=0;
double C(int mid)
{
return 1.0*(sum[mid]+x)/(mid+1);
}
double solve(int l,int r)
{
int midl,midr;
while(r-l>2){
midl=(2.0*l+r)/3;
midr=(l+2.0*r)/3;
if(C(midl)<=C(midr)) r=midr;
else l=midl;
}
return min(min(C(l),C(r)),C((l+r)/2));
}
int main ()
{
//yyy_3y
//freopen("1.in","r",stdin);
int n; scanf("%d",&n);
for(int i=1;i<=n;i++){
int type; scanf("%d",&type);
if(type==1){
scanf("%lld",&x);
sum[++cnt]=sum[cnt-1]+x;
}
else {
double tmp=solve(1,cnt-1);
ans=x-tmp;
printf("%.10f\n",ans);
}
}
return 0;
}