前言
当题目答案是一个值,且在固定范围内的话,就可以考虑二分法求解。
一、二分法的模板?
bool check(int mid){
return ;//返回true或者false;
}
int l = ma , r = sum;
while (l<r){
int mid = (r + l) /2;
if(check(mid)){//根据实际调整
r = mid;
}else{
l = mid + 1;
}
}
二 对应题目
1.蓝桥杯【打包】
显然,我们可以想到让每个人的礼物重量更接近于总体的平均值,那么就越平均,但是每个人的礼物应该比平均值多一点还是少一点,尚且不可知,但是我们可以知道这个答案的最大值,一定位于(所有礼物的重量的最大值)和(所有礼物总重之间)并尽可能让这个值小。
而且我们可以发现二分性质:如果每个人分到的礼物总重最大值小于答案,那么会出现分不完的情况,即分发礼物失败,而大于答案,会有可以成功分发礼物。
那么check()函数可以这样写
bool check(int mid){
int now = 0;//每一包礼物的当前总重
int p = 1 , i = 0;
for (int i = 0; i < n; i ++ ){
if(t[i]>mid)return false;//如果有某单个礼物重量超过检测值,不可能分发成功
now += t[i];
//如果超过检测值,表示完成一包分发
if(now > mid){
now = t[i];
p++;
}
}
//如果可以分发给更少的人,则一定可以分给更多的人
return p <= m;
}
这样就可以通过二分法找到答案。
下面为完整代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 100010;
int t[N];
int n , m ;
bool check(int mid){
int now = 0;
int p = 1 , i = 0;
for (int i = 0; i < n; i ++ ){
if(t[i]>mid)return false;
now += t[i];
if(now > mid){
now = t[i];
p++;
}
}
return p <= m;
}
int main()
{
cin >> n >> m;
int sum = 0 ;
int ma = 0;
for (int i = 0; i < n; i ++ ){
scanf("%d", &t[i]);
sum+=t[i];
ma = max(ma , t[i]);
}
//如果左值设为单个礼物的最大值,check()函数中的这个检查可以不写
int l = ma , r = sum;
while (l<r){
int mid = (r + l) /2;
if(check(mid)){
r = mid;
}else{
l = mid + 1;
}
}
cout << l;
}
2.蓝桥杯【和谐宿舍】
其实这道题和上面的【打包】很相像,都有类似的二分性质,但是其中的check()中now的计算需要做出相应的调整
bool check(int mid){
int now = 0 , idx = 0;
int p = 1 , i = 0;
for (int i = 0; i < n; i ++ ){
//如果单个高度超过了判断的最大值,直接返回false
//如果主函数的l是从ma开始的,这行代码可以不加
if(t[i]>mid)return false;
//如果作为一块挡板时的高度
now = max(now , t[i]);
//面积大于检测值,即为一块挡板
if(now * (i - idx + 1) > mid){
idx = i;
now = t[i];
p++;
}
}
return p <= m;
}
3.AcWing【社交距离I】
这道题也有贪心的性质,即我们可以选择在最大距离处安置两头奶牛,或者在最大和第二大距离分别安置一头奶牛,需要分类讨论(这里不提)
二分方法:
首先我们可以找到答案是不会超过原先的最短距离的,而且在社交距离小于答案都可以成功放置奶牛,大于答案则无法放置两头奶牛。
check()函数中应注意奶牛队列的边界处理,如以下情形
0000000000
1000000000
0000010000
我们遍历所有的间距,并对左右做好处理。完整代码如下
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
string s;
const int N = 200010;
int n , cnt;
int dis[N] ;
bool ll , rr;
bool check(int mid){
int sum = 0;
for (int i = 0; i <= cnt; i ++ ){
if(i==0 && ll|| i== cnt && rr){
//边界一端可以放牛
if(dis[i] >= 2 * mid){
sum+=2;
}if(dis[i] >= mid){
sum++;
}
}else{
if(dis[i] >= 3 * mid - 1){
sum+=2;
}if(dis[i] >= 2 * mid-1){
sum++;
}
}
if(sum>=2){
return true;
}
}
return false;
}
int main()
{
cin >>n;
getchar();
cin>>s;
for (int i = 0; i < n; i ++ )
{
if(s[i]=='0'){
dis[cnt]++;
}else{
cnt++;
}
}
if(dis[0])ll = true;
if(dis[cnt])rr = true;
//如果全为0 , 直接两边各放一头
if(!cnt){
cout << n - 1;
return 0;
}
int mi = 100010;
for (int i = 1; i < cnt; i ++ ){
mi = min(mi , dis[i]);
}
//此处的右端点值为中间间隔的最小值加1,算上最两边的间距可能为0
int l = 0 , r = mi + 1;
while(l<r)
{
int mid=(l+r+1)>>1;
if (check(mid))
l=mid;
else
r=mid-1;
}
cout << l;
return 0;
}