蓝桥杯备赛之二分专题

常用的算法二分模板

1. 在数组a[]中找大于等于x的第一个数的下标

//int ans = lower_bound(a, a + n, x) - a
//相当于下方
int l = 0, r = n - 1;
while(l < r) {
    int mid = l + r >> 1;
    if(a[mid] >= x)	r = mid;
    else l = mid + 1;
}
cout << r;

2. 在数组a[]中找大于x的第一个数的下标

//int ans = upper_bound(a, a + n, x) - a
//相当于下方
int l = 0, r = n - 1;
while(l < r) {
    int mid = l + r + 1 >> 1;
    if(a[mid] <= x) l = mid;
    else r = mid + 1;
}
cout << r;

例题

503.借教室

题目

在这里插入图片描述

在这里插入图片描述

思路

二分_借教室.png

代码
//根据题意:有单调性,区间操作  二分+差分
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 1e6 + 10;
int r[N], d[N], s[N], t[N];
LL b[N];    //差分数组
int n, m;
bool check(int mid) {
    for(int i = 1; i <= n; i++) 
        b[i] = r[i] - r[i - 1]; //初始化差分数组
    for(int i = 1; i <= mid; i++) {
        b[s[i]] -= d[i];
        b[t[i] + 1] += d[i];
    }
    for(int i = 1; i <= n; i++ ) {
        b[i] += b[i - 1];
        if(b[i] < 0)    return true;
    }
    return false;
}
int main() {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) scanf("%d", &r[i]);
    for(int i = 1; i <= m; i++) scanf("%d%d%d", &d[i], &s[i], &t[i]);
    int l = 1, r = m;
    if(!check(m)) {
        puts("0");
        return 0;
    }
    while(l < r) {
        int mid = l + r >> 1;
        if(check(mid))  r = mid;
        else l = mid + 1;
    }
    printf("-1\n%d", r);

    return 0;
}

5407.管道

题目

在这里插入图片描述

代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
typedef long long LL;
int n, len;
int l[N], s[N];
pair<int, int> q[N];
bool check(LL mid) {    //mid为当前时间
    int cnt = 0;
    for(int i = 1; i <= n; i++) {
        if(s[i] <= mid)  {
            // q[cnt++] = {mid - s[i], mid + s[i]};    别忘了区间长度限制
            int d = mid - s[i];
            LL L = max(1, l[i] - d), R = min((LL)len, (LL)l[i] + d);
            q[cnt++] = {L, R};
        }
    }
    
    sort(q, q + cnt);
    int st = -1, ed = -1;
    for(int i = 0; i < cnt; i++) {
        //这里是 ed+1 因为需要的是每个节点有水,相当于“相连”
        if(q[i].first <= ed + 1)    ed = max(ed, q[i].second);
        else {
            st = q[i].first, ed = q[i].second;
        }
    }
    
    return st == 1 && ed == len;
}
int main() {
    scanf("%d%d", &n, &len);
    for(int i = 1; i <= n; i++)
        scanf("%d%d", &l[i], &s[i]);
    LL l = 0, r = 2e9;      //最坏情况是2e9
    while(l < r) {
        LL mid = l + r >> 1;
        if(check(mid))  r = mid;
        else l = mid + 1;
    }
    cout << r;
    return 0;
}

(多路合并)

1262.鱼塘钓鱼

在这里插入图片描述

思路

在这里插入图片描述

枚举最后一次钓鱼的鱼塘, 传入参数(第k个鱼塘, 钓鱼时间)

代码如下
#include <iostream>
#include <cstring>
using namespace std;
const int N = 110;
int T, n;
int s[N], d[N], t[N], spend[N];  
int get_fish(int k) {   //在第k个鱼塘当前能钓鱼数量
    return max(0, s[k] - spend[k] * d[k]);
}
int work(int n, int T) {
    int res = 0;
    memset(spend, 0, sizeof spend);
    for(int i = 0; i < T; i++) {    //一共枚举T次,T分钟则可选择钓T次
        int t = 1;  //循环判断哪个鱼塘当前能钓鱼数最多
        for(int j = 2; j <= n; j++)
            if(get_fish(j) > get_fish(t))
                t = j;
        res += get_fish(t);  //答案加上此时钓鱼数量
        spend[t]++;     //当前鱼塘钓鱼时间++
    }
    return res;
}
int main () {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)  scanf("%d", &s[i]);
    for(int i = 1; i <= n; i++)  scanf("%d", &d[i]);    
    //走到第一个鱼塘不用时间
    for(int i = 2; i <= n; i++) cin >> t[i], t[i] += t[i - 1];
    cin >> T;
    int res = 0;
    // 枚举最后走到哪个鱼塘
    for(int i = 1; i <= n; i++) 
        res = max(res, work(i, T - t[i]));  //最后走到第i个鱼塘,钓鱼时间为T-t[i]
    cout << res;
    return 0;
}
4656.技能升级
题目:

在这里插入图片描述

思路分析:

在这里插入图片描述

在这里插入图片描述

代码和注释具体如下
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 100010;
int n, m;
int a[N], b[N];
bool check(int mid) {
    LL res = 0;
    for(int i = 0; i < n; i++)
        if(a[i] >= mid)
            res += (a[i] - mid) / b[i] + 1; //对于第i个技能所加的次数 :差值 / 公差 + 1,因为差值为0时,还需要加一次
    if(res >= m)  return true;    //判断对于最后一次为mid值所对应加技能的总次数是否满足要求m次
    return false;
}
int main() {
    scanf("%d%d", &n, &m);
    //m 的范围太大, 故从n下手,也就是枚举加的技能点值,二分答案
    for(int i = 0; i < n; i++)  scanf("%d%d", &a[i], &b[i]);
    int l = 0, r = 1e6;     //左右边界(最后一次(m)加的技能点)
    while(l < r) {
        // l 和 r 的更新,***关键***
        //这里的话由于所加技能点是从大到小排序进行选择的,故答案必然是[mid,...]的区间(mid对应的值可能不止一个),而小于mid的不符合
        int mid = l + r + 1 >> 1;
        if(check(mid))  l = mid;
        else r = mid - 1;
    }
    LL res = 0, cnt = 0;    //次数,答案
    for(int i = 0; i < n; i++) {
        if(a[i] > r) {  // r 即为最后一次加的技能点数
            int s = (a[i] - r) / b[i] + 1;  //第i个技能所加次数
            int an = a[i] - (s - 1) * b[i]; //最后一次升级的点数
            res += (LL)(a[i] + an) * s / 2;     //等差数列公式求出所加技能点和
            cnt += s;   //累加加技能次数
        }
    }
    LL ans = res - (cnt - m) * r;   //最后一个数值可能加的次数不止一次,故需要特殊处理
    cout << ans;
    return 0;
}

【5559. 摆放棋子】

【题目链接】

https://www.acwing.com/problem/content/description/5562/

【题目描述】

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

【双指针代码】
#include <iostream>
#define endl '\n'
using namespace std;
const int N = 3e5 + 10; 
int a[N]; 
//用双指针[单调性],可以用0的数量为指标(在一个区间具有单调性),枚举长度和端点 
int main() {
	int n, k;
	cin >> n >> k;
	for(int i = 0; i < n; i++)	scanf("%d", &a[i]);
	int len = 0, dest = 0;
	for(int i = 0, j = 0, zeros = 0; i < n; i++) {
		if(!a[i])	zeros++;
		while(zeros > k) {
			if(!a[j]) zeros--;
			j++;
		}
		if(i - j + 1 > len)	len = i - j + 1, dest = i;
	}
	for(int i = dest; i > dest - len; i--)
		a[i] = 1;
	cout << len << endl;
	for(int i = 0; i < n; i++)	cout << a[i] << " ";
	return 0;
} 
【二分代码】

在这里,因为很容易想到答案区间长度可以为单调的,所以我们二分查找区间长度
>= len - k即代表满足符合

#include <iostream>
using namespace std;
const int N = 3e5 + 10;
int n, k;
int a[N], s[N];

bool check(int len) {
	for(int i = n; i >= len; i--) {
		if(s[i] - s[i - len] >= len - k)
			return true;
	}
	return false;
}
int main() {
	cin >> n >> k;
	for(int i = 1; i <= n; i++)	scanf("%d", &a[i]), s[i] += s[i - 1] + a[i];
	int l = 0, r = n;
	while(l < r) {
		int mid = l + r + 1 >> 1;
		if(check(mid))	l = mid;
		else r = mid - 1;
	}
	cout << r << '\n';
	//区间长度已经锁定,那么枚举修改原数组即可
	for(int i = r; i <= n; i++)  {
		if(s[i] - s[i - r] >= r - k) {
			for(int j = i; j > i - r; j--)
				a[j] = 1;
			break;
		}
	}
	
	for(int i = 1; i <= n; i++)	cout << a[i] << " ";
	
	return 0;
}


  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值