【ACM训练四】二分、三分查找

二分查找

定义参阅:二分查找有几种写法?它们的区别是什么?
特征:每次取中点进行判断,待查找集合减半;线性函数;不断逼近
前提:查找集合有序
例题
1、Cable master(原博客链接
有n段长度分别为Li的电缆,要求把它们分割成K条长度为X的电缆,问X的最大值为多少。
题解:将X视为变量,可知它的范围为0~max; 那么问题就变成了电缆长度取X时,所得的电缆条数大于,还是等于,或小于K的问题。(线性) 用二分查找法能很快的找出K的值,不过要注意精度,直接输出时会四舍五入,五入时的结果肯定是错的,注意向下取整。

#include <cstdio>
#include <cmath>
using namespace std;
const double inf=200005.0;//较大的一个数

double l[10005];
int n,k;

bool judge (double x)
{
	int num=0;
	for(int i=0;i<n;i++)
	num+=(int)(l[i]/x);
	return num>=k;
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=0;i<n;i++)
	scanf("%lf",&l[i]);
	double left=0,right=inf;
	
	for(int i=0;i<100;i++)
	{
		double mid=left+(right-left)/2;//不用(right+left)/2,避免超出数据范围。
		if(judge(mid))
		left=mid;
		else
		right=mid;//找到刚好满足的那个
	}
	printf("%.2f\n",floor(right*100)/100);//(向下取整)
	//右边向下取整等于正确答案,左边向下取整小于正确答案
	return 0;
	
}

2、Can you find it?
给出ABC三个数列,再给出S个X,问在数列中是否有元素满足Ai+Bj+Ck=X。

#include<stdio.h>
#include<algorithm>
using namespace std;
#define X 500
int M,L,N,S;
 
int a[X+5],b[X+5],c[X+5],sum[X*X+5];
 
bool find(int x)
{
	int low = 0, high = N*L;
	while(high-1>=low){
		int mid = (low+high)/2;
  
		if(sum[mid]==x)
			return true;
		else if((sum[mid]<x))	
			low = mid + 1;
		else
			high = mid;
	}
return false;
}
int main()
{
	int x,k=0;
	while(~scanf("%d%d%d",&L,&N,&M)){
		k++;
		for(int i = 0;i < L;i ++)
			scanf("%d",&a[i]);
		for(int i = 0;i < N;i ++)
			scanf("%d",&b[i]);
		for(int i = 0;i < M;i ++)
			scanf("%d",&c[i]);
		for(int i = 0;i < L;i ++)
			for(int j = 0;j < N;j ++)
				sum[i*N+j] = a[i] + b[j];
			
		scanf("%d",&S);
		sort(sum,sum+N*L);
		
		
		int xxx[1000],ccc[1000];
		for(int i=0;i<S;i++)
		scanf("%d",&xxx[i]);
		
		for(int i = 0;i < S;i ++)
		{
			int flag = 0;
			for(int j = 0;j < M;j ++)
           {
				if(find(xxx[i]-c[j]))
                   {
					flag = 1;
					break;
				    }
			}
			if(flag)
				ccc[i]=1;
			else
				ccc[i]=0;
		}
		
		printf("Case %d:\n",k);
		for(int i=0;i<S;i++)
		{if(ccc[i])
	    printf("YES\n");
	    else
	    printf("NO\n");
		}
		
	}
	return 0;
}

3、hamburgers<二分模板题>
题目大意:给你一个字符串表示一个汉堡的结构,一个汉堡由BSC三个字母组成,然后给你你有的BSC原料的数目,和如果要购买BSC的价格。告诉你你所拥有的金钱,问最多能做出几个汉堡。

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <stdlib.h>
 
using namespace std;
 
typedef long long ll;
 
char s[105];
int nb,ns,nc;
int pb,ps,pc;
ll r;
 
int main(){
    scanf("%s",&s);
    scanf("%d%d%d",&nb,&ns,&nc);
    scanf("%d%d%d",&pb,&ps,&pc);
    scanf("%I64d",&r);
    int sb=0,ss=0,sc=0;
    int len=strlen(s);
    for(int i=0;i<len;i++){
        if(s[i]=='B') sb++;
        if(s[i]=='S') ss++;
        if(s[i]=='C') sc++;
    }
    //printf("%d %d %d ",sb,ss,sc);
    ll ans=0;
    bool f1,f2,f3;
    while(1){
        f1=false,f2=false,f3=false;
        if(nb-sb>=0) {f1=true;nb-=sb;}
        if(ns-ss>=0) {f2=true;ns-=ss;}
        if(nc-sc>=0) {f3=true;nc-=sc;}
        //printf("%d %d %d\n",nb,ns,nc);
        if(!f1&&(sb-nb)*pb<=r){
            r-=(sb-nb)*pb;
            f1=true;
            nb=0;
        }
        if(!f2&&(ss-ns)*ps<=r){
            r-=(ss-ns)*ps;
            f2=true;
            ns=0;
        }
        if(!f3&&(sc-nc)*pc<=r){
            r-=(sc-nc)*pc;
            f3=true;
            nc=0;
        }
        if(f1&&f2&&f3) ans++;
        if(!f1||!f2||!f3) break;
        if(ns*ss==0&&nb*sb==0&&nc*sc==0) break;
    }
    if(r>0){
        ll temp=r/(pb*sb+ps*ss+pc*sc);
        ans+=temp;
    }
    printf("%I64d\n",ans);
    return 0;
}

4、K best
题目大意:Demy由于家庭经济困难要卖掉她n个珠宝中的一部分来维持家庭生活,但是她想留下平均价值最高的k个珠宝,要留下哪些珠宝才能达到目的?

#include<stdio.h>
#include<math.h>
#include<algorithm>
using namespace std;
int n, k;
struct P
{
    int v, w, num;
    double y;
 
}p[100010];
bool cmp(P c, P d) {return c.y > d.y;}
bool C(double x) 
{
    for(int i=0; i<n; i++)
        p[i].y = p[i].v-x*p[i].w;
    sort(p,p+n,cmp); 
    double sum = 0;
    for(int i=0; i<k; i++)
        sum += p[i].y; 
    return sum >= 0;
}
int main()
{
 
    scanf("%d%d", &n,&k);
    for(int i=0; i<n; i++)
    {
        scanf("%d%d", &p[i].v,&p[i].w);
        p[i].num = i+1;
    }
    double lb = 0, ub = 1e6+10;
    while(ub-lb >= 1e-8)
    {
        double mid = (lb+ub)/2;
        if(C(mid)) lb = mid;
        else ub = mid;
    } 
    printf("%d", p[0].num);
    for(int i=1; i<k; i++) 
        printf(" %d", p[i].num);
    puts("");
    return 0;
}

三分查找

对于单峰函数(先增后减或先减后增),如果要求该函数在区间[Left,Right]上的极值点,可以采用三分的办法)
模板

//这里以先增后减为例
double l=Left,r=Right; 
while(l<r-eps) 
{    
    double mid1=l+(r-l)/3;    
    double mid2=mid1+(r-l)/3;    
    double f1=cal(mid1),f2=cal(mid2); 
        if(f1<f2) 
            l=mid1;
        else
            r=mid2; 
} 

例题
1、新函数F(x)定义如下:F(x) = max(Si(x)), i = 1…n。x的定义域是[0,1000]Si(x)是一个二次函数。约瑟芬娜想知道F(x)的最小值。不幸的是,对她来说解决这个问题太难了。作为一个超级程序员,你能帮助她吗?抛物线开口向上
大意:给定很多函数,让你求由这些函数最大值构成的函数的最小值
注意:不是求最大值之后最小,而是选取最大,求最小

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
using namespace std;

const double esp = 1e-9;
int n;
int a[10005][3] = {0};
double fmax(double x)
{
    double ans = a[0][0]*x*x + a[0][1]*x + a[0][2];
    for (int i = 1; i < n; ++i)
    {
        double tmp = a[i][0]*x*x + a[i][1]*x + a[i][2];
        if(tmp > ans)
            ans = tmp;
    }
    return ans;
}
double f(double x){
    return x > 0 ? x : (-x);
}

int main()
{
    int t; cin >> t;
    while(t--)
    {
        cin >> n;
        for (int i = 0; i < n; ++i)
            scanf("%d %d %d", &a[i][0], &a[i][1], &a[i][2]);
        double l, mid1, mid2, r, tmp1, tmp2;
        l = 0.0; r = 1000.0;
        do {
            mid2 = (l+r)/2;
            mid1 = (l+mid2)/2;
            tmp1 = fmax(mid1);
            tmp2 = fmax(mid2);
            if(tmp1 > tmp2)
                l = mid1;
            else
                r = mid2;
        } while(f(tmp2 - tmp1) > esp);
        printf("%.4lf\n", tmp1);
    }
    return 0;
}

2、HDU 3400 Line belt (原博客链接
给定两条线段AB,CD,在这两条线段上速度分别为P,Q,在其他地方速度为R,让求A 到 D点的最短时间
思路:我们可以知道 一定从AB线段中某一点离开(可能直接从A离开,也可能从AB段中的某一点离开),然后进入到CD中点某一点,设从AB线段的E点离开,进入到CD线段的F点。
我们假设E点已确定,现在就只需要找出F点即可,由EF段和FD段公式:time = |EF|/R + |FD|/Q
所以当F在E,D之间且R!= Q时,函数递减,当F在ED外时,函数递增
对于凸性函数,我们可以用三分解决
所以我们将EF段和FD段用三分求出最优解,再返回到E点的坐标
将AB三分,比较E_1和E_2的大小,再进行三分,实现一个三分套三分,就能解题了

#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <cmath>
#include <algorithm>
#define For(a,b) for(int a=0;a<b;a++)
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
typedef long long ll;
const int maxn =  55;
const int INF = 0x3f3f3f3f;
const int inf = 0x3f;
const double EPS = 1e-7;
struct node{
    double x,y;
    node(){};                                   //构造函数
    node(double a,double b){x = a,y = b;}       //构造函数
};
node A,B,C,D;
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 time(node a,node b){return dis(a,b)/P;}
double time(node a,node b,node c){return dis(a,c)/Q+dis(a,b)/R;}
 
double third2(node l,node r,node buf1){             //三分求出EF和DF的最佳耗时,其中buf1即为F点
    node m = node((l.x+r.x)/2,(l.y+r.y)/2);
    node mm = node((m.x+r.x)/2,(m.y+r.y)/2);
    double a,b;
    a = time(m,buf1,D);
    b = time(mm,buf1,D);
    if(abs(a-b)<=EPS)
        return a;
    return  a<b?third2(l,mm,buf1):third2(m,r,buf1);
}
 
double third1(node l,node r){
    node m = node((l.x+r.x)/2,(l.y+r.y)/2);
    node mm = node((m.x+r.x)/2,(m.y+r.y)/2);
    double ansm,ansmm;
    ansm = third2(C,D,m);                   
    ansmm = third2(C,D,mm);
    if(abs(ansm-ansmm) <= EPS)
        return ansm+time(A,mm);
    return ansm+time(A,m)<ansmm+time(A,mm)?third1(l,mm):third1(m,r);//根据总耗时大小选择要舍弃的段
}
 
int main()
{
    int t;
    cin >> t;
    while(t--){
        cin >> A.x>>A.y>>B.x>>B.y>>C.x>>C.y>>D.x>>D.y;
        cin >> P>>Q>>R;
        printf("%.2lf\n",third1(A,B));
        //cout <<third1(A,B) <<endl;
    }
    return 0;
}

3、hdu 5531 Rebuild 原博客链接
题目大意:
有 n个点,其中这 n 个点能够组成一个凸 n边形,而且是按照顺序的,这样就不用排序了,如果不给定顺序的话,应该按照极角进行排序,现在以每个点为圆心,保证两个相邻点为圆心的圆是相切的,现在求的是所有的圆的面积和,并且将每个圆的半径输出。
解题思路:
因为保证相邻圆是相切的,所以我们设第一个圆的半径为 r1,那么有如下式子成立:
r1+r2=d1
r2+r3=d2

rn+r1=dn
然后我们来解这个方程,解的过程中就会发现有一个小规律,跟 n的奇偶有关系,即:当 n 为奇数的时候,就会有唯一解能够解出 r1,r1=d1−d2+d3+…+dn2如果是偶数的话,我们发现解不出这个方程,那么怎么做的,因为求的是面积,所以最终的结果一定是关于 r1 的二次方程,又因为,二次函数我们可以通过 三分 来求得区间中的极值,我认为这个题的关键就是如何确定 r1 的三分范围,还是来考虑上述的方程,我们将其变形为:
r1≥0
d1−r1≥0
d2−d1+r1≥0

然后我们就能求得一个区间 [l,r]
, 因为这个区间中的数都是符合的,所以才进行三分。
还有一个小 trick 就是,偶数的时候我们必须求得验证一个条件:奇数边的距离是等于偶数边的距离的。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <algorithm>
using namespace std;
const int MAXN = 1e4+5;
const double PI = acos(-1.0);
const double eps = 1e-8;
struct Point{
    double x, y;
}p[MAXN];
double Dis(Point a, Point b){
    return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
double dis[MAXN], r[MAXN];
int n;
double f(double x)
{
    r[0] = x;
    double ans = r[0]*r[0];
    for(int i=1; i<n; i++){
        r[i] = dis[i-1]-r[i-1];
        ans += r[i]*r[i];
    }
    return ans;
}
double sanfen(double left, double right)
{
    double midl, midr;
    while (right-left > eps)
    {
        midl = (left + right) / 2;
        midr = (midl + right) / 2;
        if(f(midl) <= f(midr)) right = midr;
        else left = midl;
    }
    return left;
}

int main()
{
    int T; scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        for(int i=0; i<n; i++) scanf("%lf%lf", &p[i].x, &p[i].y);
        for(int i=0; i<n; i++) dis[i] = Dis(p[i], p[(i+1)%n]);
        r[0] = 0;
        for(int i=0; i<n; i++){
            if(i & 1) r[0] -= dis[i];
            else  r[0] += dis[i];
        }
        if(n & 1){
            r[0] /= 2;
            double ans = PI*r[0]*r[0];
            int ok = 0;
            if(r[0]>-eps && r[0]<=dis[0]){
                for(int i=1; i<n; i++){
                    r[i] = dis[i-1]-r[i-1];
                    if(r[i]<-eps || r[i]>dis[i]){
                        ok = 1;
                        break;
                    }
                    ans += PI*r[i]*r[i];
                }
            }
            else ok = 1;
            if(ok) puts("IMPOSSIBLE");
            else{
                printf("%.2f\n",ans);
                for(int i=0; i<n; i++) printf("%.2f\n",r[i]);
            }
        }
        else{
            double l = 0, rr = 1e9;
            if(fabs(r[0]) > eps) { puts("IMPOSSIBLE"); continue; }
            double tmp = 0;
            for(int i=1; i<n; i++){
                tmp = dis[i-1] - tmp;
                if(i & 1) rr = min(rr, tmp);
                else l = max(l, -tmp);
            }
            double ans = sanfen(l, rr);
            if(l > rr) puts("IMPOSSIBLE");
            else{
                printf("%.2f\n",f(ans)*PI);
                for(int i=0; i<n; i++) printf("%.2f\n", r[i]);
            }
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
蓝桥杯国赛是中国最大的计算机竞赛之一,参加者需要在规定的时间内完成一系列的编程题目。蓝桥杯国赛的竞争非常激烈,每年都有数以万计的学生报名参赛。 我很荣幸地告诉大家,我在最近一次的蓝桥杯国赛中获得了三等奖。这对我来说是一项非常重要的成就,也是我多年来努力学习计算机科学的回报。 在获得三等奖的过程中,我遇到了很多挑战。首先是题目的难度,比赛中出现的问题需要有深厚的计算机基础和扎实的编程能力。其次,在限定的时间内完成多个题目也需要灵活的思维和高效的执行能力。此外,比赛还有一定的压力和紧张氛围,需要保持冷静和集中精力。 在准备蓝桥杯国赛之前,我付出了大量的时间和精力。我参加了许多培训班和辅导课程,学习了算法、数据结构和编程技巧。我还刷了很多以往比赛的真题,提高了自己的编程水平。除此之外,我还积极参与学校的ACM俱乐部活动,和其他同学一起切磋学习,相互交流经验。 获得三等奖对我来说是一种认可和鼓励。它让我相信,只要付出努力并持之以恒,就能够获得回报。同时,这也增强了我的自信心,让我更加坚定地向着更高的目标迈进。 对我来说,蓝桥杯国赛不仅是一次比赛,更是一次学习和成长的机会。它推动着我不断提升自己,探索计算机科学的无限可能。我会继续努力学习,为以后的比赛做好准备,争取取得更好的成绩。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值