藍橋杯專題三:二分

二分法是一種隨處可見缺非常精妙的算法,經常能爲我們打開解答問題的突破口。二分的基礎的用法是在單調序列或單調函數中進行查找。因此當問題的答案具有單調性時,就可以通過二分把求解轉化爲判定(根據複雜度理論,判定的難度小於求解),這使得二分的運用範圍變得很廣泛。
據說,只有 10% 的程序員能寫對二分。二分的實現方法多種多樣,但是其細節之處確實需要仔細考慮。對於整數域上的二分,需要注意終止邊界、左右區間取捨時的開閉情況,避免漏掉答案或造成死循環;對於實數域上的二分,一般來說大家熟練掌握自己的一種正確寫法即可。
本篇結尾還爲大家分享了兩種二分算法模板。

題目一:數的範圍 來源:AcWing

給定一個按照升序排列的長度爲n的整數數組,以及 q 個查詢。 對於每個查詢,返回一個元素k的起始位置和終止位置(位置從0開始計數)。
如果數組中不存在該元素,則返回“-1 -1”。

輸入格式

第一行包含整數n和q,表示數組長度和詢問個數。
第二行包含n個整數(均在1~10000範圍內),表示完整數組。
接下來q行,每行包含一個整數k,表示一個詢問元素。

輸出格式

共q行,每行包含兩個整數,表示所求元素的起始位置和終止位置。 如果數組中不存在該元素,則返回“-1 -1”。

數據範圍

1≤n≤100000

1≤q≤10000

1≤k≤10000

在這裏插入圖片描述

AC代碼如下:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int MAXN = 1e6;
int n, q, a[MAXN], x;
int main() {
	cin >> n >> q;
	for(int i = 0; i < n; i++) {
		cin >> a[i];
	}
	while(q--) {
		scanf("%d", &x);
		int l = 0, r = n-1, mid;
		while(l < r) {
			mid = l + r >> 1;
			if(a[mid] < x)	l = mid + 1;	
			else	r = mid;
		}
		// 找到左端點 
		// 判斷是否存在等於x點 
		if(a[l] != x) {
			printf("-1 -1\n");
			continue;
		}
		printf("%d ", l);
		// 這裏記得將l r 初始化 
		l = 0, r = n-1;
		while(l < r) {
			mid = l + r + 1 >> 1;
			if(a[mid] > x)	r = mid - 1;
			else	l = mid;
		}
		// 找到右端點 
		printf("%d\n", l);
	}
	return 0;
}

題目:分巧克力 第八屆藍橋杯題目

兒童節那天有 K 位小朋友到小明家做客。 小明拿出了珍藏的巧克力招待小朋友們。 小明一共有 N 塊巧克力,其中第 i 塊是 Hi×Wi
的方格組成的長方形。 爲了公平起見,小明需要從這 N 塊巧克力中切出 K 塊巧克力分給小朋友們。 切出的巧克力需要滿足:
形狀是正方形,邊長是整數 大小相同 例如一塊 6×5 的巧克力可以切出 6 塊 2×2 的巧克力或者 2 塊 3×3 的巧克力。
當然小朋友們都希望得到的巧克力儘可能大,你能幫小明計算出最大的邊長是多少麼?

輸入格式

第一行包含兩個整數 N 和 K。 以下 N 行每行包含兩個整數 Hi 和 Wi。 輸入保證每位小朋友至少能獲得一塊 1×1 的巧克力。

輸出格式

輸出切出的正方形巧克力最大可能的邊長。

數據範圍

1≤N,K≤105,

1≤Hi,Wi≤105

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
typedef long long LL;
using namespace std;
const int MAXN = 1e5+10;
int n, k, MaxH, MaxW, MaxA;
int h[MAXN], w[MAXN];
int check(int m) {
	int ans = 0;
	for(int i = 0; i < n; i++) {
		ans += (h[i] / m) * (w[i] / m);
		// 長和寬能放下的最大長度的乘積 
	}
	return ans;
}
int main() {
	cin >> n >> k;
	for(int i = 0; i < n; i++) {
		scanf("%d %d", &h[i], &w[i]);
	}
	int l = 1, r = 1e5, mid; 
	while(l < r) {
		mid = l + r + 1 >> 1;
		// check條件:注意題目要求答案的最大值 
		if(check(mid) >= k)    l = mid;
		else	r = mid - 1;
	}
	// 輸出l或者r,而不是mid 
	cout << l << endl;
	return 0;
}

整數二分查找算法模板:來源 yxc

版本1

當我們將區間[l, r]劃分成[l, mid]和[mid + 1, r]時,其更新操作是r = mid或者l = mid + 1;,計算mid時不需要加1。

C++ 代碼模板:

int bsearch_1(int l, int r)
{
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    return l;
}

版本2

當我們將區間[l, r]劃分成[l, mid - 1]和[mid, r]時,其更新操作是r = mid - 1或者l = mid;,此時爲了防止死循環,計算mid時需要加1。

C++ 代碼模板:

int bsearch_2(int l, int r)
{
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    return l;
}

這個模板的來源是,因爲整數除法是向下取整的,當區間只剩下兩個數時,l 和 r 相等,我們看第二個模板,如果此時滿足 if 條件,那 mid = (l + r) / 2 = (r - 1 + r) / 2,向下取整之後 mid 還是等於 l,那麼就會進入死循環。所以有了

int mid = l + r + 1 >> 1;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章