二分
为什么我为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 n n 根原木,现在想把这些木头切割成 k k k 段长度均为 l l l 的小段木头(木头有可能有剩余)。
当然,我们希望得到的小段木头越长越好,请求出 l l l 的最大值。
木头长度的单位是 cm \text{cm} cm,原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。
例如有两根原木长度分别为 11 11 11 和 21 21 21,要求切割成等长的 6 6 6 段,很明显能切割出来的小段木头长度最长为 5 5 5。
输入格式
第一行是两个正整数 n , k n,k n,k,分别表示原木的数量,需要得到的小段的数量。
接下来 n n n 行,每行一个正整数 L i L_i Li,表示一根原木的长度。
输出格式
仅一行,即 l l l 的最大值。
如果连
1cm
\text{1cm}
1cm 长的小段都切不出来,输出 0
。
样例 #1
样例输入 #1
3 7
232
124
456
样例输出 #1
114
提示
数据规模与约定
对于 100 % 100\% 100% 的数据,有 1 ≤ n ≤ 1 0 5 1\le n\le 10^5 1≤n≤105, 1 ≤ k ≤ 1 0 8 1\le k\le 10^8 1≤k≤108, 1 ≤ L i ≤ 1 0 8 ( i ∈ [ 1 , n ] ) 1\le L_i\le 10^8(i\in[1,n]) 1≤Li≤108(i∈[1,n])。
解析
读者可以很轻松得发现,我们没什么办法求出l的最优解,但如果我们得到了
l
l
l 的值,check()
进行检查只需要
O
(
n
O(n
O(n)的复杂度。
那我们能不能枚举答案呢?
如果直接枚举的话,复杂度高达
n
×
k
n\times k
n×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 1 1 行包括三个数 L , N , K L,N,K L,N,K,分别表示公路的长度,原有路标的数量,以及最多可增设的路标数量。
第 2 2 2 行包括递增排列的 N N N 个整数,分别表示原有的 N N N 个路标的位置。路标的位置用距起点的距离表示,且一定位于区间 [ 0 , L ] [0,L] [0,L] 内。
输出格式
输出 1 1 1 行,包含一个整数,表示增设路标后能达到的最小“空旷指数”值。
样例 #1
样例输入 #1
101 2 1
0 101
样例输出 #1
51
提示
公路原来只在起点和终点处有两个路标,现在允许新增一个路标,应该把新路标设在距起点 50 50 50 或 51 51 51 个单位距离处,这样能达到最小的空旷指数 51 51 51。
50 % 50\% 50% 的数据中, 2 ≤ N ≤ 100 2 \leq N \leq 100 2≤N≤100, 0 ≤ K ≤ 100 0 \leq K \leq 100 0≤K≤100。
100 % 100\% 100% 的数据中, 2 ≤ N ≤ 100000 2 \leq N \leq 100000 2≤N≤100000, 0 ≤ K ≤ 100000 0 \leq K \leq100000 0≤K≤100000。
100 % 100\% 100% 的数据中, 0 < L ≤ 10000000 0 < L \leq 10000000 0<L≤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 N N 的正整数数列 A 1 ∼ N A_{1\sim N} A1∼N,现要将其分成 M M M( M ≤ N M\leq N M≤N)段,并要求每段连续,且每段和的最大值最小。
关于最大值最小:
例如一数列 4 2 4 5 1 4\ 2\ 4\ 5\ 1 4 2 4 5 1 要分成 3 3 3 段。
将其如下分段:
[ 4 2 ] [ 4 5 ] [ 1 ] [4\ 2][4\ 5][1] [4 2][4 5][1]
第一段和为 6 6 6,第 2 2 2 段和为 9 9 9,第 3 3 3 段和为 1 1 1,和最大值为 9 9 9。
将其如下分段:
[ 4 ] [ 2 4 ] [ 5 1 ] [4][2\ 4][5\ 1] [4][2 4][5 1]
第一段和为 4 4 4,第 2 2 2 段和为 6 6 6,第 3 3 3 段和为 6 6 6,和最大值为 6 6 6。
并且无论如何分段,最大值不会小于 6 6 6。
所以可以得到要将数列 4 2 4 5 1 4\ 2\ 4\ 5\ 1 4 2 4 5 1 要分成 3 3 3 段,每段和的最大值最小为 6 6 6。
输入格式
第 1 1 1 行包含两个正整数 N , M N,M N,M。
第 2 2 2 行包含 N N N 个空格隔开的非负整数 A i A_i Ai,含义如题目所述。
输出格式
一个正整数,即每段和最大值最小为多少。
样例 #1
样例输入 #1
5 3
4 2 4 5 1
样例输出 #1
6
提示
对于 20 % 20\% 20% 的数据, N ≤ 10 N\leq 10 N≤10。
对于 40 % 40\% 40% 的数据, N ≤ 1000 N\leq 1000 N≤1000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 5 1\leq N\leq 10^5 1≤N≤105, M ≤ N M\leq N M≤N, A i < 1 0 8 A_i < 10^8 Ai<108, 答案不超过 1 0 9 10^9 109。
解析
二分答案每段和的最大和,再带入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;
}
完结撒花