分治

递归思想

讲到分治,我们要先从简单的函数递归讲起。

\[\texttt{To iterate is human,to recurse divine.\ —— \texttt{L. Peter Deutsch}} \]

L. Peter Deutsch 这句话的意思是:“迭代的是人,递归的是神”。

数学归纳法

数学归纳法是在正整数、自然数集,乃至特殊数列上对于某命题进行证明的重要方法,需要一个基础(Base)以及一个合理的归纳假设(Induction Hypothesis)。

最简单和常见的数学归纳法是证明当 \(n\) 等于任意一个正整数时某命题成立。证明分下面两步:

  1. base:证明当 \(n=1\) 时命题成立。
  2. I.H. 假设 \(n=m\) 时命题成立,那么可以推导出在 \(n=m+1\) 时命题也成立。(\(m\) 代表任意自然数)
    结论:证明当 \(n\) 等于任意一个正整数时该命题成立。

显然,这个做法是基于递归的,就好像多米诺骨牌一样,一环扣着一环,我们推多米诺骨牌,使得它在整个自然数集或正整数集种全部倒下去。

分治

早在几百年前,我国的古书《群经平议·周官二》里就有记载:“分而治之”的思想,这就是分治的雏形,在英文里它被翻译为“divide and conquer”,divide 译为“分割”,conquer 可译为“征服”,所以,它的英文也是“分”和“治”两个操作先后进行的意思。

分治主要有两种适用情况:所贡献和并区间

算贡献

我们计算区间 \([l,r]\) 所有子集对答案的贡献,可以先设 \(mid=\frac{l+r}2\),计算 \([l,mid]\) 所有子集对答案的贡献,再计算 \((mid,r]\) 所有子集对答案的贡献,然后计算所有横跨两个区间,也就是所有 \(l_0\in[l,mid],r_0\in(mid,r]\)\([l_0,r_0]\) 对答案的贡献。

需要保证这样操作不重不漏,我们可以简单证明一下:

  • 假如我们现在的计算区间为 \([l,r]\)\(mid=\frac{l+r}2\),对于所有能对答案产生贡献的区间 \([l',r']\)

\(l'\in[l,mid],r'\in[l,mid]\),那么它会在作为 \([l,mid]\) 子集中被计算;
\(l'\in(mid,r],r'\in(mid,r]\),那么它会在作为 \((mid,r]\) 子集中被计算;
\(l'\in[l,mid],r'\in(mid,r]\),那么它会在计算所有 \([l_0,r_0]\)\(l_0\in[l,mid],r_0\in(mid,r]\))的过程中被计算;

不过如果我们这样一直递归下去,当递归到 \(r<l\) 或者 \(l=r\) 时,需要特判并结束递归。

和并区间

在 RMQ 中,我们也需要用到“合并”操作,深度理解“合并操作”对于信息学奥赛是很有帮助的,比如分治最经典的应用归并排序(下文中有介绍),就是把左右区间分别排序以后在进行合并,我们判断某区间操作能否分治,可以用“可合并”作为标准,即是否存在合理的时间复杂度,再处理过 \([l,mid]\)\((mid,r]\) 之后,能够通过合并两区间,处理出 \([l,r]\) 的所求结果。

时间复杂度

我们知道,如果我们的区间合并/算贡献操作是 \(O(n)\) 的,对于分治,时间复杂度可以主定理求解:

\[T(1)=O(1),\ T(n)=2\times T({n\over 2})+f(n) \]

属于主定理第二类情况,其中 \(a=2\)\(b=2\)\(f(n)=\Theta(n^{\log_22})=\Theta(n)\)

\[f(n)=\Theta(n^{\log_22})=\Theta(n\log n) \]

大致代码框架如下:

solve(int l,int r):
    if(l==r):
        pass;
        return;
    mid <- l+r/2;
    solve(l,mid);
    solve(mid+1,r);
    merge(l,r);

典例讲解

归并排序

在本世纪的前十几年里,pascal 时信息学竞赛的主流,由于没有 stl 和很多其他库函数的帮助,pascal 中必须手写一些数据结构和算法,归并排序就是当年没有 sort 时,选手们常写的一种排序。

在这里拿归并先讲,主要原因是它是一个十分简单、易于理解、具有代表性的分治应用,它属于我们上文总结的“和并区间”的类型。

归并排序的思路是:当给一个数组排序的时候,先将数组分为两段,然后对两段分别进行归并排序,当分隔到每段长度为 \(1\) 时,结束递归,然后对每两个区间进行合并(由于两个区间都是有序的,合并时使用双指针算法,将两个有序区间合并为一个有序区间,然后返回上一级递归,每次合并时间复杂度 \(O(n)\),空间复杂度 \(O(n)\)),所以整个归并排序时间复杂度 \(O(n\log n)\),空间复杂度 \(O(n)\)

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
int a[N];
int zc[N];
void msort(int a[], int st, int ed) {
    if (st == ed) return;
    int mid = (st + ed) >> 1;
    msort(a, st, mid), msort(a, mid + 1, ed);
    for (int i = st, j = mid + 1, k = 1; k <= ed - st + 1; ++k)
        if (i > mid) zc[k] = a[j], ++j;
        else if (j > ed) zc[k] = a[i], ++i;
        else if (a[i] > a[j]) zc[k] = a[j], ++j;
        else zc[k] = a[i], ++i;
    for (int i = 1, j = st; j <= ed; ++i, ++j)
        a[j] = zc[i];
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    msort(a, 1, n);
    for (int i = 1; i <= n; ++i) cout << a[i] << ' ';
    return 0;
}

扩展:偏序问题与 cdq 分治,有兴趣可以去读一读。

posted @ 2023-03-19 13:46  abensyl  阅读(43)  评论(0)    收藏  举报  来源
//雪花飘落效果