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);
}

二、二分答案

二分答案是猜答案、验证合法性

核心思想:

  1. 确定答案的取值范围(最小值min,最大值max)

  2. 每次猜测中间值mid,编写check函数验证mid是否合法

  3. 根据合法性收缩区间,最终锁定最优答案(最大合法值/最小合法值)

适用条件:答案具有单调性:若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. 将数组从中间拆分,递归拆分为左右两个子数组,直到子数组长度为1(天然有序)

  2. 分别排序左右子数组

  3. 将两个有序子数组,合并为一个完整的有序数组

时间复杂度:稳定\(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;
}
posted @ 2026-07-20 13:44  HaneDaniko  阅读(50)  评论(0)    收藏  举报