7.20 分治 讲义
核心思想:将一个复杂的大问题,拆分成若干个规模更小、结构相同的子问题,解决子问题后,合并子问题答案得到原问题答案。
一、二分查找
1. 核心原理
二分查找是分治最基础的应用,仅适用于有序序列。
核心思路:每次取序列中间位置元素,与目标值对比,排除一半无效区间,不断缩小查找范围,直到找到答案或区间为空。
时间复杂度:\(O(\log n)\)
2. 适用场景
-
有序数组中查找某个数是否存在
-
查找有序数组中目标数的第一次出现位置、最后一次出现位置
-
查找有序数组中大于/小于目标数的第一个元素
区分普通查找、左边界查找、右边界查找的不同写法
3. C++ 完整代码实现
#include <bits/stdc++.h>
using namespace std;
// 1. 基础二分查找:找到目标值返回下标,未找到返回-1
int binarySearch(vector<int>& a, int target) {
int left = 0, right = a.size() - 1;
while (left <= right) {
int mid = (left+right)/2;
if (a[mid] == target) {
return mid; // 找到目标,直接返回下标
}
else if (a[mid] < target) {
left = mid + 1; // 目标在右区间
}
else {
right = mid - 1; // 目标在左区间
}
}
return -1; // 未找到
}
// 2. 左边界二分:查找目标值第一次出现的下标,未找到返回-1
int findLeftBound(vector<int>& a, int target) {
int left = 0, right = a.size() - 1;
while (left < right) {
int mid = (left+right)/2;
if (a[mid] >= target) {
right = mid; // 收缩右边界,锁定左端点
} else {
left = mid + 1;
}
}
return a[left] == target ? left : -1;
}
// 3. 右边界二分:查找目标值最后一次出现的下标,未找到返回-1
int findRightBound(vector<int>& a, int target) {
int left = 0, right = a.size() - 1;
while (left < right) {
int mid = (left+right+1)/2; // 向上取整,避免死循环 (4 5) 查找4处元素 (4+5)/2=4, a[4]=要查找元素,死循环。
if (a[mid] <= target) {
left = mid; // 收缩左边界,锁定右端点
} else {
right = mid - 1;
}
}
return a[left] == target ? left : -1;
}
int main() {
// 测试样例:有序数组
vector<int> arr = {1, 2, 2, 2, 3, 5, 7, 9};
int x = 2;
cout << "基础查找2的下标:" << binarySearch(arr, x) << endl;
cout << "2的左边界下标:" << findLeftBound(arr, x) << endl;
cout << "2的右边界下标:" << findRightBound(arr, x) << endl;
return 0;
}
dfs 写法:
int binarySearch(vector<int>& a, int target ,int l,int r){
int mid=(l+r)/2;
if(a[mid]==target) return mid;
else if(a[mid]<target) return binarySearch(a,target,mid+1,r);
else return binarySearch(a,target,l,mid-1);
}
二、二分答案
二分答案是猜答案、验证合法性。
核心思想:
-
确定答案的取值范围(最小值min,最大值max)
-
每次猜测中间值mid,编写check函数验证mid是否合法
-
根据合法性收缩区间,最终锁定最优答案(最大合法值/最小合法值)
适用条件:答案具有单调性:若mid合法,则所有小于/大于mid的值一定合法
3. 代码实现
P1577 切绳子
题目描述
有 \(N\) 条绳子,它们的长度分别为 \(L_i\)。如果从它们中切割出 \(K\) 条长度相同的绳子,这 \(K\) 条绳子每条最长能有多长?答案保留到小数点后 \(2\) 位(直接舍掉 \(2\) 位后的小数)。
输入格式
第一行两个整数 \(N\) 和 \(K\),接下来 \(N\) 行,描述了每条绳子的长度 \(L_i\) 。
输出格式
切割后每条绳子的最大长度。答案与标准答案误差不超过 \(0.01\) 或者相对误差不超过 \(1\%\) 即可通过。
输入输出样例 #1
输入 #1
4 11
8.02
7.43
4.57
5.39
输出 #1
2.00
说明/提示
对于 \(100\%\) 的数据 \(0<L_i\leq 100000.00,0<n\leq 10000,0<k\leq 10000\)
弱化版代码
#include <bits/stdc++.h>
using namespace std;
vector<int> len;
int n, k;
// check函数:验证长度x是否可行(能否截出至少k段)
bool check(int x) {
int cnt = 0;
for (int i = 0; i < n; i++) {
cnt += len[i] / x; // 每根绳子可截取的段数
if (cnt >= k) return true; // 提前剪枝,优化效率
}
return cnt >= k;
}
int main() {
cin >> n >> k;
len.resize(n);
int max_len = 0;
for (int i = 0; i < n; i++) {
cin >> len[i];
max_len = max(max_len, len[i]);
}
// 二分答案区间:最小1,最大绳子最长长度
int left = 1, right = max_len;
int ans = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (check(mid)) {
ans = mid; // mid合法,记录答案,尝试更长
left = mid + 1;
} else {
right = mid - 1; // mid不合法,缩短长度
}
}
cout << ans << endl;
return 0;
}
代码核心逻辑:合法就更新答案、放大区间,不合法就缩小区间,最终得到最优解。
三、归并排序
归并排序是稳定的排序算法
-
将数组从中间拆分,递归拆分为左右两个子数组,直到子数组长度为1(天然有序)
-
分别排序左右子数组
-
将两个有序子数组,合并为一个完整的有序数组
时间复杂度:稳定\(O(n \log n)\),最坏、最好情况无波动
空间复杂度:\(O(n)\)(需要临时数组存储合并结果)
2. C++ 完整代码实现
基础归并排序代码(可直接用于竞赛排序,附带详细注释)
#include <bits/stdc++.h>
using namespace std;
vector<int> temp; // 临时数组,统一开辟空间,避免重复创建
// 归并合并:将左右两个有序区间合并
void merge(vector<int>& a, int l, int mid, int r) {
int i = l, j = mid + 1;
int cnt = 0;
// 双指针合并两个有序区间
while (i <= mid && j <= r) {
if (a[i] <= a[j]) {
temp[cnt++] = a[i++];
} else {
temp[cnt++] = a[j++];
}
}
// 处理剩余元素
while (i <= mid) temp[cnt++] = a[i++];
while (j <= r) temp[cnt++] = a[j++];
// 将临时数组的有序数据拷贝回原数组
for (int k = 0; k < cnt; k++) {
a[l + k] = temp[k];
}
}
// 归并排序递归主体
void mergeSort(vector<int>& a, int l, int r) {
if (l >= r) return; // 递归终止:区间只有一个元素,无需排序
int mid = l + (r - l) / 2;
mergeSort(a, l, mid); // 分:排序左区间
mergeSort(a, mid + 1, r); // 分:排序右区间
merge(a, l, mid, r); // 合:合并左右有序区间
}
int main() {
vector<int> arr = {9, 5, 1, 4, 3, 7, 2, 8, 6};
temp.resize(arr.size()); // 初始化临时数组
mergeSort(arr, 0, arr.size() - 1);
// 输出排序结果
for (int x : arr) {
cout << x << " ";
}
return 0;
}

浙公网安备 33010602011771号