核心思想:
将一个大问题 拆成一些子问题, 求解完子问题后,进行合并,降低时间复杂度
一、经典例题
1、求序列中最大子串和
对于答案而言, 因为连续, 我们现在将一个区间分成两个部分,最终答案为
(1)左区间的最大值
(2)右区间的最大值
(3)左区间的最大后缀 + 右区间的最大前缀
三者取最大值,因为每个操作其实本质上都是相同的, 直接分治成子问题, 最后合并
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 1000005;
int a[N];
ll cdq(int n, int l, int r) {
if(l == r) return a[l];
int mid = l + r >> 1;
ll sum = max(cdq(n, l, mid), cdq(n, mid + 1, r));
ll t1 = 0, t2 = 0, t = 0;
for(int i = mid + 1; i <= r; ++ i)
t += a[i], t1 = max(t1, t);
t = 0;
for(int i = mid; i >= l; -- i)
t += a[i], t2 = max(t2, t);
sum = max(sum, t1 + t2);
return sum;
}
int main() {
int n;
scanf("%d", &n);
for(int i = 0; i < n; ++ i) scanf("%d", &a[i]);
printf("%lld\n", cdq(n, 0, n - 1));
return 0;
}
2、确定二叉树
核心思想 :
利用 (1)前序遍历 : 根左右 (2)中序遍历 : 左根右 (3)后序遍历 : 左右根
确定好根节点的位置,分成左子树 和 右子树 两个区间, 依次递归处理!!!
(1) 根据前序遍历 + 后序遍历 确定一颗二叉树
#include<bits/stdc++.h>
using namespace std;
vector<int> ans;
void deal(int l1, int r1, int l2, int r2, const vector<int>& pre, const vector<int>& suf) {
if(l1 > r1) return;
if(l1 == r1) {
ans.push_back(pre[l1]);
return ;
}
int x = pre[l1 + 1], pos = -1;
for(int i = l2; i <= r2; ++ i)
if(suf[i] == x) {
pos = i;
break;
}
deal(l1 + 1, l1 + 1 + pos - l2, l2, pos, pre, suf);
ans.push_back(pre[l1]);
deal(l1 + 1 + pos - l2 + 1, r1, pos + 1, r2 - 1, pre, suf);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int n;
cin >> n;
vector<int> pre(n), suf(n);
for(int i = 0; i < n; ++ i) cin >> pre[i];
for(int i = 0; i < n; ++ i) cin >> suf[i];
deal(0, n - 1, 0, n - 1, pre, suf);
for(int x : ans) cout << x << " ";
cout << "\n";
return 0;
}
(2) 中序 + 后序 确定 二叉树
#include<bits/stdc++.h>
using namespace std;
void deal(int l1, int r1, int l2, int r2, const string &s1, const string &s2) {
if(l2 > r2 || l1 > r1) return;
if(l1 == r1) {
cout << s1[l1];
return;
}
int pos = -1;
char c = s2[r2];
cout << c;
for(int i = l1; i <= r1; ++ i)
if(c == s1[i]) {
pos = i;
break;
}
deal(l1, pos - 1, l2, l2 + pos - 1 - l1, s1, s2);
deal(pos + 1, r1, l2 + pos - 1 - l1 + 1, r2 - 1, s1, s2);
}
int main() {
string s1, s2;
cin >> s1 >> s2;
int n = s1.size();
deal(0, n - 1, 0, n - 1, s1, s2);
return 0;
}
(3) 前序 + 中序 确定后序
#include<bits/stdc++.h>
using namespace std;
void deal(int l1, int r1, int l2, int r2, const string &s1, const string &s2) {
if(l2 > r2 || l1 > r1) return;
if(l1 == r1) {
cout << s1[l1];
return;
}
int pos = -1;
char c = s1[l1];
for(int i = l2; i <= r2; ++ i)
if(c == s2[i]) {
pos = i;
break;
}
deal(l1 + 1, l1 + 1 + pos - 1 - l2, l2, pos - 1, s1, s2);
deal(l1 + 1 + pos - 1 - l2 + 1, r1, pos + 1, r2, s1, s2);
cout << c;
}
int main() {
string s1, s2;
cin >> s1 >> s2;
int n = s1.size();
deal(0, n - 1, 0, n - 1, s1, s2);
return 0;
}
!!!宗旨: 先找到根节点, 分成左右子树, 重复递归!!!
3、FBI 树
于给定的长度为 2 𝑛 2 n ,仅由 "0" "0" 和 "1" "1" 组成的字符串 𝑠 s ,请按下方的规则构造出一棵二叉树 𝑇 T : ∙ ∙记当前子树 𝑇 𝑖 T i 的根节点为 root 𝑖 root i ; ∙ ∙记当前子树分得的字符串 𝑠 𝑖 s i 的长度大于 1 1 ,则将 𝑠 𝑖 s i 从中间分开,分为等长的左右子串 𝑠 𝑖 ′ s i ′ 和 𝑠 𝑖 ′ ′ s i ′′ ; ∙ ∙由 𝑠 𝑖 ′ s i ′ 递归构造 root 𝑖 root i 的左子树 𝑇 𝑖 ′ T i ′ ,由 𝑠 𝑖 ′ ′ s i ′′ 递归构造 root 𝑖 root i 的右子树 𝑇 𝑖 ′ ′ T i ′′ ;
由于难以将整棵树完整的输出,所以我们定义节点的简称: ∙ ∙若当前节点分得字符串仅 "0" "0" 构成,那么该节点简称 B B 节点; ∙ ∙若当前节点分得字符串仅 "1" "1" 构成,那么该节点简称 I I 节点; ∙ ∙若当前节点分得字符串既包含 "0" "0" 又包含 "1" "1" ,那么该节点简称 F F 节点。
你只需要输出所构造二叉树的后序遍历。每一个节点使用简称表示。
#include<bits/stdc++.h>
using namespace std;
char deal(int l, int r, const string &s) {
if(l == r) {
if(s[l] == '1') {
cout << 'I';
return 'I';
}
else {
cout << 'B';
return 'B';
}
}
char c1 = deal(l, l + r >> 1, s);
char c2 = deal((l + r) / 2 + 1, r, s);
if(c1 == c2 && c1 == 'B') {
cout << 'B';
return 'B';
}
if(c1 == c2 && c2 == 'I') {
cout << 'I';
return 'I';
}
cout << 'F';
return 'F';
}
int main() {
int n;
cin >> n;
string s;
cin >> s;
deal(0, (int)s.size() - 1, s);
cout << "\n";
return 0;
}
4、在一堆点中找出距离最近的两个点
思路:分治
将一堆数据 分成两部分,距离的最小值为
(1)左堆内的最小值 (2)右堆内的最小值 (3)两个交界处的最小值
PS 下面的代码是正常代码的变式,比较特殊,而且初始值不可以超过2e5 + 10, 否则会超时
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 1;
struct node{
int x, y;
bool st;
};
vector<node> a;
double dis(node t1, node t2) {
if(t1.st == t2.st) return N;
int dx = t1.x - t2.x, dy = t1.y - t2.y;
return sqrt(1LL * dx * dx + 1LL * dy * dy); // 转换成long long, 否组会爆 int
}
double cdq(int l, int r) {
if(l >= r) return N;
if(l + 1 == r) return dis(a[l], a[r]);
int mid = l + r >> 1;
double ans = min(cdq(l, mid), cdq(mid + 1, r));
// 两堆的最小值
// 接下来求相邻两堆之间的距离最小值
vector<node> tmp;
for(int i = l; i <= r; ++ i)
if(fabs(a[i].x - a[mid].x) <= ans)
tmp.push_back(a[i]);
for(int i = 0; i < tmp.size(); ++ i)
for(int j = i + 1; j < tmp.size(); ++ j) {
if(tmp[j].y - tmp[i].y > ans) break; // 减少循环次数,防止超时
if(tmp[j].st == tmp[i].st) continue;
ans = min(ans, dis(tmp[i], tmp[j]));
}
return ans;
}
void deal() {
int n;
cin >> n;
a.resize(2 * n);
for(int i = 0; i < n; ++ i) cin >> a[i].x >> a[i].y, a[i].st = 1;
for(int i = n; i < 2 * n; ++ i) cin >> a[i].x >> a[i].y, a[i].st = 0;
sort(a.begin(), a.end(), [&](node t1, node t2){
if(t1.x == t2.y) return t1.y < t2.y;
return t1.x < t2.x;
});
// 先按照 x 的大小 排序, 之所在这里给 y 排序,是为了方便,因为在接下来的tmp数组需要对y排序
double ans = cdq(0, 2 * n - 1);
printf("%.03lf\n", ans);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
while(t --)
deal();
return 0;
}