• 基本算法——二分答案经典模型例题


    序言

      对于单调性或二段性的对象一般会考虑二分答案。

      把该问题转化为给定一个值mid,判定是否可行,进而缩小范围。

    模型

      1.最大值最小&最小值最大

      此类问题对于答案从属于右边的,则选用“r=mid”的模板;对于答案从属于左边的,则选用“l=mid”的模板。

      2.最接近某个值的答案

      这类问题其实可以根据绝对值的性质,将其划分成两个子问题。

      大于等于该值的答案中最小的&小于等于该值的答案中最大的

      那么便转化成上一类问题了。

      3.最大化或最小化某个值

      根据单调性二分即可。

    典题详析

      1.洛谷P1948电话线/Acwing 340.通信线路

    题面分析:

      题目要求“剩余的电缆中,最贵的那条的最少花费”,这就是一个典型的“最大值最小”的模型。

    算法思路:

      二分路径上第k+1长的边的权值,使其最小化。
      对于路径上边权大于该值的边,是必须用一次免费机会的。那么把其值记为1;
      否则是不需用掉免费机会的,则把其值记为0;
      然后跑一遍01最短路即可。

      代码如下:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <queue> 
    using namespace std;
    const int  N = 1e3+10,M = 2e4+10;
    struct Edge{
        int to,next,w;
    }edge[M]; int idx;
    int h[N];
    int vis[N],dis[N];
    int n,m,k;
    
    void add(int u,int v,int w)
    {
        edge[++idx].w=w;
        edge[idx].to=v;
        edge[idx].next=h[u];
        h[u]=idx;
    }
    
    bool check(int mid)  //SLF优化的SPFA最短路
    {
        deque<int> q;
        memset(dis,0x3f,sizeof dis);
        memset(vis,0,sizeof vis);
        dis[1]=0;
        vis[1]=1;
        q.push_back(1);
        while(!q.empty())
        {
            int now=q.front();
            q.pop_front();
            vis[now]=0;
            for(int i=h[now];~i;i=edge[i].next)
            {
                int to=edge[i].to,w;
                if(edge[i].w<=mid)w=0;else w=1;
                if(dis[to]>dis[now]+w)
                {
                    dis[to]=dis[now]+w;
                    if(!vis[to])
                    {
                        if(!q.empty()&&dis[to]<dis[q.front()])
                            q.push_front(to);
                        else q.push_back(to);
                        vis[to]=1;
                    }
                }
            }
        }
    
        if(dis[n]<=k)return true;
        else return false;
    }
    
    int main()
    {
        memset(h,-1,sizeof h);
        scanf("%d%d%d",&n,&m,&k);
        int maxx=-2e9;
        for(int i=1;i<=m;i++)
        {
            int u,v,w;
            scanf("%d%d%d",&u,&v,&w);
            add(u,v,w);
            add(v,u,w);
            maxx=max(maxx,w);
        }
        int l=0,r=maxx;
        while(l<r)
        {
            int mid=l+r>>1;
            if(check(mid))r=mid;
            else l=mid+1;
        }
        if(!check(l))puts("-1");
        else printf("%d
    ",l);
        return 0;
    }

      2. NOIP提高组2011 聪明的质监员 洛谷P1314 / Acwing 499

    题面分析:

      仔细思考,不难发现如下性质:

      W越大,则重量大于等于W的矿石就会越少,那么Y就越小;

      W越小,则重量大于等于W的矿石就会越多,那么Y就越大。

      也就是说,Y与W呈负相关,Y随W单调变化。

    算法思路:

      我们二分W的值,然后进行判定。

      找出满足条件“使得Y小于等于S”的W中的最小值,即满足条件的Y的最大值。

      那么最后再算一遍W-1的Y值,即是Y大于等于S的Y的最小值。

      对于满足条件的矿石个数与价值的区间求和,我们很容易想到用前缀和来处理。

      代码如下:

    #include <iostream>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    using namespace std;
    const int N =2e5+10,M=2e5+10;;
    long long s;
    int n,m;
    int sum_n[N],sum_v[N];
    int w[N],v[N],l[M],r[M];
    
    long long valid(int mid)
    {
        memset(sum_n,0,sizeof sum_n);
        memset(sum_v,0,sizeof sum_v);
        
        for(int i=1;i<=n;i++)
        {
            if(w[i]>=mid)
            {
                sum_n[i]=sum_n[i-1]+1;
                sum_v[i]=sum_v[i-1]+v[i];
            }
            else
            {
                sum_n[i]=sum_n[i-1];
                sum_v[i]=sum_v[i-1];
            }
        }
        
        long long ans=0;
        for(int i=1;i<=m;i++)
        {
            ans+=(long long)(sum_n[r[i]]-sum_n[l[i]-1])*(sum_v[r[i]]-sum_v[l[i]-1]);
        }
        return ans;
    }
    
    int main()
    {
        scanf("%d%d%lld",&n,&m,&s);
        int L=2e9,R=-2e9;
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&w[i],&v[i]);
            L=min(L,w[i]);
            R=max(R,w[i]);
        }
        for(int i=1;i<=m;i++)scanf("%d%d",&l[i],&r[i]);
        
        long long ans=2e18;
        while(L<R)
        {
            int mid=L+R>>1;
            long long res=valid(mid);
            if(res<=s)R=mid;
            else L=mid+1;
        }
        ans=min(abs(valid(L)-s),abs(valid(L-1)-s));
        printf("%lld
    ",ans);
        return 0;
    } 
    

      


      3. NOIP提高组2012 借教室 洛谷P1083 / Acwing 503

    题面分析:

      当前一份的订单已经无法满足的时候,那么后续的订单也必然无法满足。

      于是我们发现了能否满足的订单数的单调性。

    算法思路:

      二分订单数,即题目要求的第一份无法满足的订单编号。

      那么对于每次的二分值,进行判定:

      到当前这份订单为止,对于每一天要租借的教室的数量进行累加,看原有的教室数量是否足够。

      每次判定都需要对区间进行加减操作,那么我们自然想到用差分进行处理。

      代码如下:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    
    using namespace std;
    
    const int N = 1e6+10;
    
    int n,m;
    int s[N],t[N],d[N],f[N],c[N];
    
    bool check(int mid)
    {
        memcpy(c,f,sizeof c);
        for(int i=1;i<=mid;i++)
        {
            c[s[i]]-=d[i];
            c[t[i]+1]+=d[i];
        }
        int sum=0;
        for(int i=1;i<=n;i++)
        {
            sum+=c[i];
            if(sum<0)return true;
        }
        return false;
    }
    
    int main()
    {
        scanf("%d%d",&n,&m);
        int la=0;
        for(int i=1;i<=n;i++)
        {
            int a;scanf("%d",&a);
            f[i]=a-la;la=a;
        }
        for(int i=1;i<=m;i++)scanf("%d%d%d",&d[i],&s[i],&t[i]);
        int l=1,r=n+1;
        while(l<r)
        {
            int mid=l+r>>1;
            if(check(mid))r=mid;
            else l=mid+1;
        }
        if(l==n+1)puts("0");
        else printf("-1
    %d
    ",l);
    }
  • 相关阅读:
    微服务治理热门技术揭秘:无损上线
    云原生事件驱动引擎(RocketMQEventBridge)应用场景与技术解析
    阿里云解决方案架构师张平:云原生数字化安全生产的体系建设
    Vue配置对象
    js 实现reduce
    js 实现bind
    Vue简介
    关于Nodejs 技术架构 如何与C++通信
    cdn原理与优缺点 cdn跨域问题
    Vue模板语法 插值语法与指令语法
  • 原文地址:https://www.cnblogs.com/ninedream/p/11626425.html
Copyright © 2020-2023  润新知