codevs---二分法

codevs---二分法

为了更进一步优化算法,很多时候,二分法,是不二选择,二分法好理解,但程序写法存在许多不同,写起来考虑的情况就比较多,反而难编写,考虑量变引起质变,需要进行积累,总结规律,找出最通用的写法。

http://codevs.cn/problem/?problemset_id=1#

分类筛选->按算法->其他->二分法

//1038 一元三次方程求解 2001年NOIP全国联赛提高组
//很久以前采用暴力 枚举 通过该题,现再尝试二分法
//样例通过,提交AC 2018-2-4 10:41
//P1024 一元三次方程求解 https://www.luogu.org/problemnew/show/P1024 洛谷 提交AC 2018-2-4 10:42
#include <stdio.h>
double a,b,c,d;
double f(double x){
    return a*x*x*x+b*x*x+c*x+d;//此处写成 a*x*x*x*+b*x*x+c*x+d; 一直没查出,折腾了半小时,排查排到这,仔细阅读,才查出
}
int main(){
    int i;
    double x1,x2,mid;
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);//此处写成 scanf("%lf%lf%lf%lf",&a,&b,&c,&c);跟踪之后才查出
    for(i=-100;i<100;i++){
        x1=i,x2=i+1;
        if(f(x1)==0)printf("%.2lf ",x1);//边界数据处理
        else if(f(x1)*f(x2)<0){
            while(x1+0.001<x2){
                mid=(x1+x2)/2;
                if(f(x1)*f(mid)<=0)x2=mid;
                else x1=mid;
            }
            printf("%.2lf ",x1);
        }
    }
    return 0;
}
//因不明,一元三次方程是增函数,还是减函数,参考上述代码,在二分法中,只将x1与x2互换,样例通过,提交,竟然AC。

//代码如下:2018-2-4 15:39 如何解释?按理来讲,二分法适用于增函数或减函数,该题增减性不明,为何能用?

#include <stdio.h>
double a,b,c,d;
double f(double x){
    return a*x*x*x+b*x*x+c*x+d;//此处写成 a*x*x*x*+b*x*x+c*x+d; 一直没查出,折腾了半小时,排查排到这,仔细阅读,才查出
}
int main(){
    int i;
    double x1,x2,mid;
    scanf("%lf%lf%lf%lf",&a,&b,&c,&d);//此处写成 scanf("%lf%lf%lf%lf",&a,&b,&c,&c);跟踪之后才查出
    for(i=-100;i<100;i++){
        x1=i,x2=i+1;
        if(f(x1)==0)printf("%.2lf ",x1);//边界数据处理
        else if(f(x1)*f(x2)<0){
            while(x1+0.001<x2){
                mid=(x1+x2)/2;
                if(f(x2)*f(mid)<=0)x1=mid;
                else x2=mid;
            }
            printf("%.2lf ",x1);
        }
    }
    return 0;
}
翻阅各种题解,偶然看到一句,

 //由f(x1)*f(x2)<0很容易 得知真正的根在(x1,x2)之间
明白了,还是题目,没读进心里,题中,已有说明 f(x1)*f(x2)<0 ,则在 (x1 , x2) 之间一定有一个 根。上述两种写法,

if(f(x1)*f(mid)<=0)x2=mid; (x1,mid)之间必定有一根,故x2=mid

if(f(x2)*f(mid)<=0)x1=mid; (mid,x2)之间必定有一根,故 x1=mid

对二分法有更深入理解,若约束条件,能明确限定数据范围,对数据变化要求,未必一定要是增函数,或是减函数。2018-2-4 16:47

//2072 分配房间
//第一步,快排
//该题的难度在于,判断条件的编写,
//有了判断条件,之后才是二分法
//根据上述思路,进行手动模拟,发现还是有些感觉的。
//样例模拟如下:
//位置排好序:x[1]=1 x[2]=2 x[3]=4 x[4]=8 x[5]=9
//left=1 right=(9-1)/(3-1)=4 理由是第一个女友放在x[1]的位置即1 安置区间介于1-4
//mid=(1+4)/2=2 开始安置x[1] x[3] x[4]能安置3个女友 空间太小或刚好 故left=mid
//left=2 right=4 mid=(2+4)/2=3 开始安置 x[1] x[3] x[8] 能 置3个女友 空间太小或刚好 故left=mid
//此时left=3 right=4 left+1<=right 退出二分法循环
//十分高兴,按自己的思路编写,样例通过,提交一次性AC,二分法真是有进步了。2018-2-5 21:09
#include <stdio.h>
int x[1000100],n,m;
int judge(int d){//d当前距离,返回值0 right=mid,返回值1 left=mid;
    int pre,cnt,i;//cnt 统计可安置女友个数
    pre=1,cnt=1,i=pre+1;//pre存储上一个女友位置
    while(i<=n){
        while(i<=n&&x[i]-x[pre]<d)i++;
        if(i<=n)cnt++,pre=i,i=pre+1;
    }
    if(cnt<m)return 0;//距离太大
    else return 1;//距离太小或刚好 此处写法  将答案放在left而不是right
}
void quicksort(int left,int right){//自小到大排序
    int i=left,j=right,mid=x[(left+right)/2],t;
    while(i<=j){
        while(x[i]<mid)i++;
        while(x[j]>mid)j--;
        if(i<=j)t=x[i],x[i]=x[j],x[j]=t,i++,j--;//此处漏了i++,j-- 查了会,快排要常编
    }
    if(left<j)quicksort(left,j);
    if(i<right)quicksort(i,right);   
}
int main(){
    int i,left,right,mid;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&x[i]);
    quicksort(1,n);//快排,自小到大
    left=1,right=(x[n]-x[1])/(m-1);
    while(left+1<right){
        mid=(left+right)/2;
        if(judge(mid))left=mid;
        else right=mid;
    }
    printf("%d",left);
    return 0;
}
//2703 奶牛代理商 XII
//该题与http://codevs.cn/problem/2072/     2072 分配房间 基本就是同一题,很高兴再做一遍,增强信心,提高熟练程度
//详解,请看 2072 分配房间 解答
//程序编写完成,测试样例,未通过,发现 该题 与 2072 分配房间 不同,该题 是求 最短的最远距离
//而2072 分配房间  是求 最小距离的最大值
//二分法未果,还是先将该题做出
//因 2<=N<=10^5 故O(n)可以将该题做出
//样例通过,提交AC 2018-2-6
#include <stdio.h>
#define m 4
int x[100100],n,d[100100],f[100100];//d[i] x[i] x[i-1]之间距离 f[i] d[i] d[i-1] d[i-2]之间最大距离
int max(int a,int b){
    return a>b?a:b;
}
void quicksort(int left,int right){//自小到大排序
    int i=left,j=right,mid=x[(left+right)/2],t;
    while(i<=j){
        while(x[i]<mid)i++;
        while(x[j]>mid)j--;
        if(i<=j)t=x[i],x[i]=x[j],x[j]=t,i++,j--;
    }
    if(left<j)quicksort(left,j);
    if(i<right)quicksort(i,right);
}
int main(){
    int i,left,right,mid,min=999999999;
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&x[i]);
    quicksort(1,n);
    for(i=2;i<=n;i++)d[i]=x[i]-x[i-1];//d[i]两点间距离
    for(i=4;i<=n;i++)f[i]=max(d[i],max(d[i-1],d[i-2]));//f[i] 连续三段距离中 ,最大距离  
    for(i=4;i<=n;i++)
        if(min>f[i])min=f[i];
    printf("%d",min);
    return 0;
}
//3135 River Hopscotch
//二分法是肯定的,因1<=L<=1000000000
//根据距离移走石头,移走石头个数过多,距离过大 right=mid ,移走石头个数过少或刚好,距离过小或刚好 left=mid
//此种二分,难在 判断 函数 的编写
//从样例来看,石头离按距离,需要排序,采用快排
//样例通过,提交 测试点3,4 WA ,对于codevs网站满意之处,在于,看到了第一个错误的测试点 数据
//
//3135 River Hopscotch
//测试数据
//输入:
//250 8 4
//3
//45
//32
//12
//56
//89
//203
//109
//输出:

//24

//上述样例思考过程如下图:


//发现当cnt==m时,要将left=mid而不是right=mid读者可以自己模拟
//上述数据通过,样例通过,提交AC 2018-2-8 22:55
//http://ybt.ssoier.cn:8088/problem_show.php?pid=1247中提交,同样AC 河中跳房子
//此中题目的难点在于,cnt==m时,是left=mid还是right=mid,读者还是要多模拟,笔者有更多经验时,争取将此类问题说清。
#include <stdio.h>
int L,n,m,d[50100];
void quicksort(int left,int right){//快排,自小到大
    int i=left,j=right,mid=d[(left+right)/2],t;
    while(i<=j){
        while(d[i]<mid)i++;
        while(d[j]>mid)j--;
        if(i<=j)t=d[i],d[i]=d[j],d[j]=t,i++,j--;
    }
    if(left<j)quicksort(left,j);
    if(i<right)quicksort(i,right);
}
int judge(int x){//移走石头个数过多,距离过大 right=mid 返回值为1 ,移走石头个数过少或刚好,距离过小或刚好 left=mid 返回值为0
    int pre,i,cnt;
    pre=0,i=pre+1,cnt=0;//下面代码,边界处理花了很长时间,思考时间跨度超过一天,不过思维确实得到很好的锻炼
    while(i<=n){//此处写成 while(i<=n+1)
        while(i<=n&&d[i]-d[pre]<x)i++,cnt++;//此处写成 while(i<=n+1&&d[i]-d[pre]<x)i++,cnt++;
        if(i<=n)pre=i,i=pre+1;//此处写成 if(i<=n+1)pre=i,i=pre+1;
    }
    //printf("x=%d cnt=%d m=%d d[%d]=%d\n",x,cnt,m,pre,d[pre]);
    if(d[n+1]-d[pre]<x)cnt++;//最后一块石头要单独判定,若 d[n+1]-d[pre]<x 移走d[pre]这块石头
    if(cnt>m)return 1;//此处写成if(cnt>=m)return 1;//因是求最小距离,故将等号放于 right=mid这一侧  后发现错误
    else return 0;
}
int main(){
    int i,left,right,mid;
    scanf("%d%d%d",&L,&n,&m);
    for(i=1;i<=n;i++)scanf("%d",&d[i]);
    quicksort(1,n),d[0]=0,d[n+1]=L;
    left=1,right=L;
    while(left+1<right){
        mid=(left+right)/2;
        //printf("%d %d\n",judge(mid),mid);
        if(judge(mid))right=mid;
        else left=mid;
    }
    printf("%d",left);//此处写成 printf("%d",right);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值