基本算法——二分答案經典模型例題


序言

  對於單調性或二段性的對象一般會考慮二分答案。

  把該問題轉化為給定一個值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\n",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\n",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\n%d\n",l);
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM