二分答案及例题讲解

二分

为什么我为OI泪目?因为我菜得离谱......

引入

用来解决有序问题,这里的有序是广义的有序,如果一个数组中的左侧或者右侧都满足某一种条件,而另一侧都不满足这种条件,也可以看作是一种有序(如果把满足条件看做 1,不满足看做 0,至少对于这个条件的这一维度是有序的)。换言之,二分搜索法可以用来查找满足某种条件的最大(最小)的值。

一言以概之:最大值最小化,最小值最大化
这些问题往往难以通过数据求出最优解,于是我们通过二分枚举问题最优解,再放入数据检查的方式,来得到答案。
发一下二分答案板子

int l=0,r=inf;
while(l<=r)
{
	int mid=(l+r)/2;
    if(check(mid)) l=mid+1;
    else r=mid-1;
}

其中check()表示你写的答案检查函数,l,r表示左右区间
这个板子可以说是万金油,l,r不用思考就这样写,但最后check()符合条件返回0还是1,以及答案是在l还是r就要自己思考了。
接下来有三道例题,读者可选读。

例题

木材加工 P2440黄

题目背景

要保护环境

题目描述

木材厂有 \(n\) 根原木,现在想把这些木头切割成 \(k\) 段长度\(l\) 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 \(l\) 的最大值。

木头长度的单位是 \(\text{cm}\),原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 \(11\)\(21\),要求切割成等长的 \(6\) 段,很明显能切割出来的小段木头长度最长为 \(5\)

输入格式

第一行是两个正整数 \(n,k\),分别表示原木的数量,需要得到的小段的数量。

接下来 \(n\) 行,每行一个正整数 \(L_i\),表示一根原木的长度。

输出格式

仅一行,即 \(l\) 的最大值。

如果连 \(\text{1cm}\) 长的小段都切不出来,输出 0

样例 #1

样例输入 #1
3 7
232
124
456
样例输出 #1
114

提示

数据规模与约定

对于 \(100\%\) 的数据,有 \(1\le n\le 10^5\)\(1\le k\le 10^8\)\(1\le L_i\le 10^8(i\in[1,n])\)

解析

读者可以很轻松得发现,我们没什么办法求出l的最优解,但如果我们得到了 \(l\) 的值,check()进行检查只需要\(O(n\))的复杂度。
那我们能不能枚举答案呢?
如果直接枚举的话,复杂度高达\(n\times k\)(10^13) ,考虑到答案具有单调性,如果l变大那么段数肯定变小(起码不会出现段数增大的情况)
想清楚这些我们就可以开始二分答案了
check()函数检查的是段数是否<=条件,符合条件说明段数太多,长度太短,于是长度应当缩小,应舍弃左区间的答案,应将左端点右移到mid。(注意这些判断性的语句,错了一个你的代码都有调好久)
再提一句:
二分右端点应当开大到可能区间的2倍,
最好在来一发#define int long long
以减少见祖宗的概率(保险起见,不然可能会调死你)

下面解析简略(^U^)ノ~YO

CODE1

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
#define int long long
const int tsn=1e5+5;
const int inf=1e9+5;
int a[tsn];
int n,k;
int check(int kis)
{
    if(kis==0) return 1;
    long long sum=0;
    for(int i=1;i<=n;i++) sum+=a[i]/kis;
    // cout<<kis<<" :sum: "<<sum<<endl;
    if(sum>=k) return 1;
    return 0;
}
signed main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    #ifndef ONLINE_JUDGE
        freopen("00in.txt","r",stdin);
        freopen("00out.txt","w",stdout);
    #endif
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    int l=0,r=inf;while(l<=r)
    {
        // cout<<"l,r:"<<l<<" "<<r<<endl;
        if(check(mid)) l=mid+1;
        else r=mid-1;
    }
    cout<<r;
    return 0;
}


[TJOI2007] 路标设置 P3583黄题

题目背景

B 市和 T 市之间有一条长长的高速公路,这条公路的某些地方设有路标,但是大家都感觉路标设得太少了,相邻两个路标之间往往隔着相当长的一段距离。为了便于研究这个问题,我们把公路上相邻路标的最大距离定义为该公路的“空旷指数”。

题目描述

现在政府决定在公路上增设一些路标,使得公路的“空旷指数”最小。他们请求你设计一个程序计算能达到的最小值是多少。请注意,公路的起点和终点保证已设有路标,公路的长度为整数,并且原有路标和新设路标都必须距起点整数个单位距离。

输入格式

\(1\) 行包括三个数 \(L,N,K\),分别表示公路的长度,原有路标的数量,以及最多可增设的路标数量。

\(2\) 行包括递增排列的 \(N\) 个整数,分别表示原有的 \(N\) 个路标的位置。路标的位置用距起点的距离表示,且一定位于区间 \([0,L]\) 内。

输出格式

输出 \(1\) 行,包含一个整数,表示增设路标后能达到的最小“空旷指数”值。

样例 #1

样例输入 #1
101 2 1
0 101
样例输出 #1
51

提示

公路原来只在起点和终点处有两个路标,现在允许新增一个路标,应该把新路标设在距起点 \(50\)\(51\) 个单位距离处,这样能达到最小的空旷指数 \(51\)

\(50\%\) 的数据中,\(2 \leq N \leq 100\)\(0 \leq K \leq 100\)

\(100\%\) 的数据中,\(2 \leq N \leq 100000\), \(0 \leq K \leq100000\)

\(100\%\) 的数据中,\(0 < L \leq 10000000\)

解析

二分答案每个路标之间的间隔即可,不用担心中间间隔不等的情况,因为我们求的是最大间距。

CODE2

#include<bits/stdc++.h>
#define mid (L+R>>1)
using namespace std;
int l,n,k;
const int tsn=1e7+5;
const int inf=1e9+10;
int b[tsn];
int check(int key)
{
    if(key==0) return 0;
    long long sum=0;
    for(int i=1;i<=n;i++)
        if(b[i]-b[i-1]-key>0) sum+=(b[i]-b[i-1]-1)/key;
    if(sum<=k) return 1;
    return 0;
}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    #ifndef ONLINE_JUDGE
        freopen("00in.txt","r",stdin);
        freopen("00out.txt","w",stdout);
    #endif
    cin>>l>>n>>k;
    for(int i=1;i<=n;i++) cin>>b[i];
    int L=0,R=inf;while(L<=R)//ans in R
    {
        if(check(mid)) R=mid-1;
        else L=mid+1;
    }
    cout<<L;
    return 0;
}


数列分段 Section II P1182黄题

题目描述

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

关于最大值最小:

例如一数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段。

将其如下分段:

\[[4\ 2][4\ 5][1] \]

第一段和为 \(6\),第 \(2\) 段和为 \(9\),第 \(3\) 段和为 \(1\),和最大值为 \(9\)

将其如下分段:

\[[4][2\ 4][5\ 1] \]

第一段和为 \(4\),第 \(2\) 段和为 \(6\),第 \(3\) 段和为 \(6\),和最大值为 \(6\)

并且无论如何分段,最大值不会小于 \(6\)

所以可以得到要将数列 \(4\ 2\ 4\ 5\ 1\) 要分成 \(3\) 段,每段和的最大值最小为 \(6\)

输入格式

\(1\) 行包含两个正整数 \(N,M\)

\(2\) 行包含 \(N\) 个空格隔开的非负整数 \(A_i\),含义如题目所述。

输出格式

一个正整数,即每段和最大值最小为多少。

样例 #1

样例输入 #1
5 3
4 2 4 5 1
样例输出 #1
6

提示

对于 \(20\%\) 的数据,\(N\leq 10\)

对于 \(40\%\) 的数据,\(N\leq 1000\)

对于 \(100\%\) 的数据,\(1\leq N\leq 10^5\)\(M\leq N\)\(A_i < 10^8\), 答案不超过 \(10^9\)

解析

二分答案每段和的最大和,再带入check()段数是否符合条件即可。

CODE3

#include<bits/stdc++.h>
#define mid (l+r>>1)
using namespace std;
const int tsn=1e5+5;
const int inf=1e9;
int n,m;
int a[tsn];
int check(int key)
{
    int sum=1;
    for(int i=1,tans=0;i<=n;i++)
    {
        // if(tans>key) sum++,tans=0; 
        if(a[i]>key) return 0;
        tans+=a[i];
        if(tans>key) sum++,tans=a[i];
    }
    if(sum<=m) return 1;
    return 0;
}
int main()
{
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    #ifndef ONLINE_JUDGE
        freopen("00in.txt","r",stdin);
        freopen("00out.txt","w",stdout);
    #endif
    cin>>n>>m;
    int maxn=0;
    for(int i=1;i<=n;i++) cin>>a[i];
    int l=1,r=2*inf;while(l<=r)
    {
        if(check(mid)) r=mid-1;
        else l=mid+1;
    }
    cout<<l;
    return 0;
}


完结撒花

在这里插入图片描述
在这里插入图片描述

posted @ 2024-08-09 16:38  左边之上  阅读(148)  评论(0)    收藏  举报