二分答案及例题讲解
二分
为什么我为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\) 段。
将其如下分段:
第一段和为 \(6\),第 \(2\) 段和为 \(9\),第 \(3\) 段和为 \(1\),和最大值为 \(9\)。
将其如下分段:
第一段和为 \(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;
}
完结撒花



浙公网安备 33010602011771号