分治
递归思想
讲到分治,我们要先从简单的函数递归讲起。
L. Peter Deutsch 这句话的意思是:“迭代的是人,递归的是神”。
数学归纳法
数学归纳法是在正整数、自然数集,乃至特殊数列上对于某命题进行证明的重要方法,需要一个基础(Base)以及一个合理的归纳假设(Induction Hypothesis)。
最简单和常见的数学归纳法是证明当 \(n\) 等于任意一个正整数时某命题成立。证明分下面两步:
- base:证明当 \(n=1\) 时命题成立。
- 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)\) 的,对于分治,时间复杂度可以主定理求解:
属于主定理第二类情况,其中 \(a=2\),\(b=2\),\(f(n)=\Theta(n^{\log_22})=\Theta(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 分治,有兴趣可以去读一读。

浙公网安备 33010602011771号