zoukankan      html  css  js  c++  java
  • 二分答案(学习笔记)

    二分的基本用途是在单调序列或单调函数中做查找操作,特别注意一定要具有单调性.

    整数定义域上的二分(模板):

    int erfen(int l,int r){
      int l=1,r=n,ans;
      while(l<=r){
        int mid=(l+r)>>1;
        if(check(mid)) ans=mid,l=mid+1;
        else r=mid-1;
      }
      return ans;
    }
    

    实数域上的二分(模板):

    double erfen(double l,double r){
      double mid;
      while(r-l>eps){
        mid=(l+r)/2.0;
        if(check(mid)) r=mid;
        else l=mid;
      }  
      return l;//返回l和r都行
    }
    
    

    eps是要确定好的精度,一般题目要求保留k位小数,则取eps=(10^{-(k+2)})

    有时精度不容易确定或表示,就干脆采用循环固定次数的二分方法,往往结果的精度更高(实数二分常常卡精度)

    for(int i=0;i<100;i++){
      double mid=(l+r)/2;
      if(check(mid)) r=mid;
      else l=mid;
    }
    

    二分答案:求最小值最大(或最大值最小)的问题,常常选用二分法求解,同时配合贪心,DP等其他算法检验答案的合理性(即check函数),将最优化问题转化为判定性问题;

    例题:数列分段II(洛谷1182)

      对于给定的一个长度为N的正整数数列A,现要将其分成M(M≤N)段,并要求每段连续,且每段和的最大值最小。

      求最大值最小,典型的二分答案题;

    #include<bits/stdc++.h>
    #define LL long long
    #define INF 99999999
    using namespace std;
    int n,m,l,r,mid;
    int a[100005];
    bool check(int x){//二分每段和的最大值的最小值
        int sum=0,tot=1;
        for(int i=0;i<n;i++){
            sum+=a[i];//sum表示当前段的值的和
            if(sum>x){
                tot++;//tot表示分的段数,表示新开一段
                sum = a[i];
            }
        }
        if(tot>m) return 1;
        //分的段数比m大的话,表示答案在更大的区间内
        else return 0;
    }
    int main(){
        scanf("%d%d",&n,&m);//n个数,分成m段
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);//读入n个数
            r+=a[i];//r把n个数的和记录下来
            l=max(l,a[i]);//l记录n个数中最大的数
            //l,r就是二分的两个端点,每段和的值一定大于等于l,同时小于等于r
        }
        while(l<r){//二分模板
            mid=(l+r)/2;
            if(check(mid)==1) l=mid + 1;
            else r=mid;
        }
        printf("%d
    ",r);
        return 0;}
    

    例题:扩散(洛谷1661)

      一个点每过一个单位时间就会向四个方向扩散一个距离,两个点a、b连通,记作e(a,b),当且仅当a、b的扩散区域有公共部分。连通块的定义是块内的任意两个点u、v都必定存在路径e(u,a0),e(a0,a1),…,e(ak,v)。给定平面上的n给点,问最早什么时刻它们形成一个连通块。

      方法一:二分时间t,运用并查集判断连通块;

      方法二:假设任意两点之间有边,相当于求所有点构成的最小生成树中最长的一条边。把两点扩散连接的时长作为边的权值,开一个结构体存边,然后用kruskal算法求最小生成树,找到其中最长的边即可。

    方法一:

    #include<bits/stdc++.h>
    using namespace std;
    long long ans;
    int n;
    int xx[51],yy[51],father[51];
    int find(int x){
        while(father[x]!=x) x=father[x];
        return x;
    }//并查集的查询操作,没有路径压缩优化
    int main(){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
            scanf("%d%d",&xx[i],&yy[i]);
        int l=0,r=1000000000;//二分时间t可能在的区间
        while(l<=r){
            int mid=(l+r)/2;
            for(int i=1;i<=n;i++)
              father[i]=i;//初始化并查集
            for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++){
      int dis=abs(xx[i]-xx[j])+abs(yy[i]-yy[j]);
      //求出两点间的曼哈顿距离
                if(dis<=mid*2){
                    int r1=find(i),r2=find(j);
                    if(r1!=r2) father[r2]=r1;
                }
       //因为是两个点同时扩散,所以mid*2
       //表示这两个点可以连通
            }
            int cnt=0;//连通块的数量
            for(int i=1;i<=n;i++){
                if(father[i]==i) cnt++;
            }//有多少个点指向自己,就有多少个连通块
            if(cnt==1){
            //表示时间t还可以更小,或者就是t
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        printf("%lld",ans);
        return 0;
    }
    

    方法二:把题目要求转换一下,假设任意两点之间有边,相当于求所有点构成的最小生成树中最长的一条边。

    把两点扩散连接的时长作为边的权值,开一个结构体存边,然后用kruskal算法求最小生成树,找到其中最长的边即可。

    #include<bits/stdc++.h>
    using namespace std;
    struct Edge{
        int x,y,val;
    }edge[3000];
    int father[3000];
    int cnt,n,ans;
    bool cmp(Edge x,Edge y){
        return x.val<y.val;
    }
    void in(){
        int x[51],y[51];
        cin>>n;
        for(int i=1;i<=n;i++)
            cin>>x[i]>>y[i];
        for(int i=1;i<=n;i++)   
        for(int j=1;j<i;j++){
            edge[++cnt].x=i;
            edge[cnt].y=j;
            //建边,存图
            int dis;
            dis=abs(x[i]-x[j])+abs(y[i]-y[j]);
            if(dis&1) edge[cnt].val=(dis>>1)+1; 
            else  edge[cnt].val=(dis>>1);
        }//把两点扩散连接的时长作为边的权值
    }
    int find(int x) {
        if(father[x]!=x) 
        	father[x]=find(father[x]);
        return father[x];
    }//并查集查询操作,路径压缩优化
    void kruskal(){
        int p=1;
        for(int i=1;i<=n;i++)
            father[i]=i;//并查集初始化
        for(int i=1;i<=cnt;i++)
            int u=find(edge[i].x);
            int v=find(edge[i].y);
            if(u!=v){
                father[u]=v;
                ans=max(edge[i].val,ans);
                //找最长的一条边
                p++;
                if(p==n) return;
            }
        return ;
    }
    int main()
    {
        in();
        sort(edge+1,edge+cnt+1,cmp);//结构体排序
        kruskal();
        cout<<ans<<endl;
        return 0;
    }
    
    

    例题:Best Cow Fences(POJ2018)

    例题:[Usaco2005 feb]愤怒的牛(BZOJ1734)

    例题:Innovative Business

  • 相关阅读:
    Lock
    synchronized
    线程可见性与原子性
    线程安全问题
    MySQL索引背后的数据结构和原理
    求一颗二叉树中两个节点的最低公共父节点
    Session not active, could not store state 的解决方法
    https nginx 设置
    第三方支付系统
    facebook页面种简单测试js调用flash开放的js接口的方法
  • 原文地址:https://www.cnblogs.com/PPXppx/p/10324264.html
Copyright © 2011-2022 走看看