关于二分操作的基本应用

前言:二分答案最重要的一点就是答案具有连续性,即有单调性的连续函数。

一:可以验证答案是否正确,来改变答案区间

如:求零点,求最接近元素。

还可以用于某些去掉重复元素的操作。

这一类比较简单,不做详细解释

二:最大化最小值/最小化最大值

如noip2015:

 

2257: [NOIP2015]跳石头

Description

一年一度的“跳石头”比赛又要开始了! 

这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有N块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。 

为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走M块岩石(不能移走起点和终点的岩石)。

Input

输入第一行包含三个整数L,N,M,分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。  接下来N行,每行一个整数,第i行的整数Di(0 < Di < L)表示第i块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

Output

输出只包含一个整数,即最短跳跃距离的最大值。 

Sample Input

25 5 2
2
11
14
17
21

Sample Output

4

HINT

样例说明】  将与起点距离为2和14的两个岩石移走后,最短的跳跃距离为4(从与起点距离17的岩石跳到距离21的岩石,或者从距离21的岩石跳到终点)。 

【数据规模与约定】  对于20%的数据,0 ≤ M ≤ N ≤ 10。  对于50%的数据,0 ≤ M ≤ N ≤ 100。  对于100%的数据,0 ≤ M ≤ N ≤ 50,000,1 ≤ L ≤ 1,000,000,000。

 

下面代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int lenth[50001];
int len,n,m;
bool check(int step)
{
    int count=0,temp=0,i;
    for(i=1;i<=n;i++)
    {
        if(lenth[i]-lenth[temp]>=step)
        {
            temp=i;
        }
        else count++;
    }
    if(count>m) 
    return false;
    if(len-lenth[temp]<step) 
    return false;
    return true;
}
int main()
{
    int i,j;
    scanf("%d%d%d",&len,&n,&m);
    int l=0,r,mid;
    for(i=1;i<=n;i++)
    {
        scanf("%d",&lenth[i]);
    }
    r=len;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(check(mid)) 
        l=mid;
        else r=mid;
    }
    if(check(r))
    cout<<r;
    else
    cout<<l;
     
}
View Code


三:关于二分验证的方法

1.按照题意模拟验证

比较简单不详细说明

2.dp验证

如:

 魔棒

时间限制: 1 Sec  内存限制: 128 MB 提交: 50  解决: 8 [提交][状态]

题目描述

有一个英雄,初始生命值是hp(生命值无上限),在接下来的n秒内,每秒会受到一次伤害,第i秒受到的伤害值为a[i]。这个英雄有一个道具“魔杖”,魔杖的初始能量为0,每受到一次伤害,积攒一点能量。在英雄受到伤害后,可以立即释放魔棒中的能量,恢复15*[能量点数]的生命值,且魔棒的点数清零。释放能量有施法间隔cdcd是正整数),即相邻的两次释放的时间间隔至少有cd秒。任何时刻当hp<=0时视为死亡,问这个英雄存活下来的前提下,cd的值最大可以是多少?

    注意,若a[i]为负,受到“伤害”后实际上生命值是增加的,魔棒仍然积攒能量。

 

输入

第一行两个正整数n,hp,含义如题目所述。

第二行n个整数,分别是a[1]..a[n]

 

输出

一个数,最大的cdcd是一个正整数。

如果cd没有上限,输出“No upper bound.”;如果无论如何都不能存活,输出-1

 

样例输入

7 30
20 5 30 4 10 5 20

样例输出

2

提示

对于30%的数据,n<=12

对于100%的数据,n<=500|a[i]|<=1000

 

#include<iostream>
#include<cstdio>
#include<cstring>
#define ll long long
using namespace std;
ll a[550],hp,dp[501][501][2],sp[501][2],n;
bool check(int mid)
{
    memset(dp,0,sizeof(dp));
    memset(sp,0,sizeof(sp));
    dp[0][0][0]=hp;
    sp[0][0]=hp;
    int i,j;
    for(i=1;i<=n;i++)
    {
        for(j=2;j<=i;j++)
        {
            dp[i][j][0]=dp[i-1][j-1][0]-a[i];
            dp[i][j][1]=dp[i-1][j-1][1]-a[i];
            if(dp[i][j][0]>0)
            {
                sp[i][0]=max(sp[i][0],dp[i][j][0]+j*15);
            }
            if(dp[i][j][1]>0&&j>=mid)
            {
                sp[i][1]=max(sp[i][1],dp[i][j][1]+j*15);
            }
        }
        dp[i][1][0]=dp[i-1][0][0]-a[i];
        if(dp[i][1][0]>0)
        {
            sp[i][0]=max(sp[i][0],dp[i][1][0]+15);
        }
        int step=max(sp[i-1][0],sp[i-1][1]);
        if(step>=a[i])
        {
            dp[i][0][1]=step+15-a[i];
        }
        dp[i][1][1]=step-a[i];
    }
    for(i=0;i<=n;i++)
    {
        if(dp[n][i][0]>0||dp[n][i][1]>0)
        return 1;
    }
    return 0;
}
int main()
{
    ll i,j;
    ll count=0;
    int step=0;
    scanf("%lld%lld",&n,&hp);
    ll ppp=hp;
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
    }
    hp=ppp;
    for(i=1;i<=n;i++)
    {
        hp=hp-a[i];
        count++;
        if(hp<=0&&step==0)
        {
            hp=hp+(count*15);
            step=1;
        }
        if(hp>0&&i==n)
        {
            cout<<"No upper bound.";
            return 0;
        }
    }
    hp=ppp;
    ll l=0,r=n,mid;
    while(l+1<r)
    {
        mid=(l+r)/2;
        if(check(mid)==1)
        {
            l=mid;
        }
        if(check(mid)==0)
        {
            r=mid;
        }
    }
    if(l==0)
    cout<<"-1";
    else
    cout<<l;
}
View Code

 


dp验证比较常见,不再概述

3.最短路验证

问题 A: 收费站(cost.pas/c/cpp)

题目描述

    在某个遥远的国家里,有n个城市。编号为1,2,3,……,n。
    这个国家的政府修建了m条双向的公路。每条公路连接着两个城市。沿着某条公路,开车从一个城市到另一个城市,需要花费一定的汽油。
    开车每经过一个城市,都会被收取一定的费用(包括起点和终点城市)。所有的收费站都在城市中,在城市间的公路上没有任何的收费站。
    小红现在要开车从城市u到城市v(1<=u,v<=n)。她的车最多可以装下s升的汽油。在出发的时候,车的油箱是满的,并且她在路上不想加油。
    在路上,每经过一个城市,她要交一定的费用。如果她某次交的费用比较多,她的心情就会变得很糟。所以她想知道,在她能到达目的地的前提下,她交的费用中最多的一次最少是多少。这个问题对于她来说太难了,于是她找到了聪明的你,你能帮帮她吗?

输入

    第一行5个正整数,n,m,u,v,s。分别表示有n个城市,m条公路,从城市u到城市v,车的油箱的容量为s升。

    接下来有n行,每行1个正整数,fi。表示经过城市i,需要交费fi元。

    再接下来有m行,每行3个正整数,ai,bi,ci(1<=ai,bi<=n)。表示城市ai和城市bi之间有一条公路,如果从城市ai到城市bi,或者从城市bi到城市ai,需要用ci升汽油。

 

输出

    仅一个整数,表示小红交费最多的一次的最小值。

    如果她无法到达城市v,输出-1。

 

样例输入

 4 4 2 3 8
 8
 5
 6
 10
 2 1 2
 2 4 1
 1 3 4
 3 4 3

样例输出

8

提示

    对于60%的数据,满足n<=200,m<=10000,s<=200

    对于100%的数据,满足n<=10000,m<=50000,s<=1000000000

    对于100%的数据,满足ci<=1000000000,fi<=1000000000,可能有两条边连接着相同的城市。

代码:

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define inf 0X3F3F3F3F
using namespace std;
 
int n,m,u,v,s,f[10005],dist[10005];
vector<int>g[10005],w[10005];
 
struct data
{
    int d,id;
    friend bool operator < (data a,data b)
    {
        return a.d>b.d;
    }
};
 
void Dij(int s,int *d,int limit)
{
    priority_queue<data>pq;
    for(int i=1;i<=n;i++) d[i]=inf;
    pq.push((data){0,s});
    d[s]=0;
 
    while(!pq.empty())
    {
        data t=pq.top(); pq.pop();
        int i=t.id;
        if(f[i]>limit) continue;
        if(t.d>d[i]) continue;
        d[i]=t.d;
 
        for(int k=0;k<g[i].size();k++)
        {
            int j=g[i][k],c=w[i][k];
            if(f[j]>limit) continue;
            if(d[i]+c<d[j])
            {
                d[j]=d[i]+c;
                pq.push((data){d[j],j});
            }
        }
    }
}
 
int main()
{
    scanf("%d%d%d%d%d",&n,&m,&u,&v,&s);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&f[i]);
    }
 
    for(int i=1;i<=m;i++)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        g[x].push_back(y);
        g[y].push_back(x);
        w[x].push_back(z);
        w[y].push_back(z);
    }
 
    int A=0,B=1000000000,ans;
    while(A<=B)
    {
        int mid=(A+B)/2;
        Dij(u,dist,mid);
        if(dist[v]<=s)
        {
            B=mid-1;
            ans=mid;
        }
        else
        {
            A=mid+1;
        }
    }
    if(B==1000000000)
    {
        printf("-1\n");
    }
    else printf("%d\n",ans);
    return 0;
}
View Code

 

验证的方法还有很多,目前遇到的只有这几种,遇到后会再做补充。

 

 

posted @ 2017-07-17 19:44  ashon37w  阅读(490)  评论(3编辑  收藏  举报