首页 > 其他分享 >分治学习笔记

分治学习笔记

时间:2022-12-28 09:45:14浏览次数:63  
标签:int 复杂度 分治 笔记 学习 区间 排序 我们 逆序

算法思想

分治的主要思想就是分而治之,即把一个大问题分成若干个小问题,先去解决这些小问题,再去解决大问题。分治是一个思想,我们通过一些实际应用来感受一下。

  • 归并排序

归并排序是一种稳定的排序算法,最好和最坏时间复杂度均为 \(O(n \log n)\),是一种极其优秀的排序算法,它的原理如下:

假设我们要对一个 \(n\) 长度的数组排序,那么首先我们先把 \([1,\frac{n+1}{2})\) 和 \([\frac{n+1}{2},n+1)\) 排好序,然后再把两段合到一起就可以整体排好序。而那段数的排序也重复以上过程,知道只剩一个数为止。

把两段已经排好序的数组合并是很简单的,只要维护两个指针就可以在 \(O(n)\) 的时间内排好序。这就是分治,不过它的时间复杂度为什么是 \(O(n \log n)\) 的呢?我们设 \(T(n)\) 表示对 \(n\) 长度数组归并排序的时间复杂度,则会有 \(T(n)=2T(n/2)+O(n)\),然后《算法导论》上讲了一个主方法,根据那个方法就可以知道 \(T(n)=O(n \log n)\)。不过本人比较菜,不会,于是我给一下章节:

《算法导论》第四章 4.5 用主方法求解递归式

这就是分治的其中一个应用之一,分治能解决很多问题,下面通过以下习题来一一讲述。

一些习题

P1177 【模板】快速排序

题目链接:P1177 【模板】快速排序

思路:

我们可以用归并排序来解决这一问题,归并排序上面已经说过,于是这里给一下代码。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int n, a[100005] = {0}, t[100005] = {0};
void slv(int l, int r) {
	if (l + 1 == r)
		return;
	int m = (l + r) / 2;
	slv(l, m), slv(m, r);
	for (int i = l, j = m, cur = l; i < m || j < r; )
		if (i < m && (j == r || a[i] < a[j]))
			t[cur++] = a[i++];
		else
			t[cur++] = a[j++];
	for (int i = l; i < r; i++)
		a[i] = t[i];
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	slv(1, n + 1);
	for (int i = 1; i <= n; i++)
		printf("%d ", a[i]);
	return 0;
} 

P1908 逆序对

题目连接:P1908 逆序对

思路:

这里涉及归并排序一个用处——求逆序对,逆序对是一个序列中满足 \(1 \le i < j \le n\) 且 \(a_i > a_j\) 的 \((i,j)\) 个数。

我们采用分治的思路,考虑对于一个 \(n\) 长度的序列,我们设 \(mid = \lfloor\frac{n+1}{2}\rfloor\)。我们先求出 \([1,mid)\) 和 \([mid,n+1)\) 两个区间的逆序对个数,并且把这两个区间排好序,然后再求 \([1,n+1)\) 这个区间的逆序对个数。而那两个区间也是继续一分为二。

我们可以用双指针来求出所有 \(i \in [1,mid),j\in[mid,n+1)\) 的逆序对个数。
我们枚举所有的 \(i\),并且维护 \(j\) 指向 \([mid,n+1)\) 中第一个满足 \(a_j \ge a_i\) 的位置,那么说明 \([mid,j)\) 中的所有数都可以和 \(a_i\) 组成逆序对,于是我们直接把答案加上 \(j-mid\) 即可。
因为 \(j\) 做多移动 \(n-mid+1\) 次,所以时间复杂度史 \(O(n)\) 的。

我们在求出了逆序对后还要对两端区间进行合并,使得整个区间是有序的,这样才能去求解更大的区间。

时间复杂度和归并排序一样,也是 \(O(n \log n)\)。

代码:

#include <iostream>
#include <cstdio>
#include <map>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
int n, a[500005] = {0}, t[500005] = {0};
long long slv(int l, int r) {
	if (l + 1 == r)
		return 0ll;
	int m = (l + r) / 2;
	long long ans = slv(l, m) + slv(m, r);
	for (int i = l, j = m; i < m; i++) {
		while (j < r && a[j] < a[i])
			j++;
		ans += j - m;
	} 
	for (int i = l, j = m, cur = l; i < m || j < r; )
		if (i < m && (j == r || a[i] < a[j]))
			t[cur++] = a[i++];
		else
			t[cur++] = a[j++];
	for (int i = l; i < r; i++)
		a[i] = t[i];
	return ans;
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	printf("%lld\n", slv(1, n + 1));
	return 0;
} 

String Reversal

题目链接:CF1430E String Reversal

思路:

这道题和 P1966 [NOIP2013 提高组] 火柴排队 几乎完全一样,所以这里只讲一下这道题的做法。

逆序对有一个作用,一个数组的逆序对个数就是这个数组冒泡排序的最少交换次数。我们首先把原字符串的每个字符记作一个二元组,以字符为第一关键字,编号为第二关键字,然后我们再把字符串反转,同样是每个元素记作一个二元组,字符为第一关键字,编号为第二关键字。

我们把两个数组进行从小到大的排序,这时编号相同的元素就是我们要再交换后放在一起的,于是我们新建一个数组 \(c\),对于 \(i \in [1,n]\),\(c_{a_i.second}=b_i.second\),然后我们只需要求出 \(c\) 的逆序对个数即可。时间复杂度 \(O(n \log n)\)。

代码:提交记录


P5094 [USACO04OPEN] MooFest G 加强版

题目链接:P5094 [USACO04OPEN] MooFest G 加强版

思路:

直接暴力复杂度是 \(O(n^2)\) 的,考虑如何优化。我们没办法去处理最大值和绝对值(\(dis(i,j)=|x_i-x_j|\)),于是我们考虑把听力和坐标变有序,可以用归并。

我们首先对坐标 \(x\) 从小到大排序,这样的话每次归并我们依然是先处理 \([l,m),[m,r)\) 两个区间内任两头牛的总和(\(m\) 是中点),再求出每个区间各一只的总和。我们发现,由于我们最开始就按坐标拍好了序,所以对于所有 \(l \le i < m ,m \le j<r\) 都有 \(x_i \le x_j\),所以我们就不需要去管绝对值了。

于是我们归并时是按照听力的大小从小到大归并,因为外部顺序已经确定,所以内部是可以打乱的。

考虑中间如何求出答案。我们采用双指针的方法,先枚举 \(i \in[l,m)\),维护 \(j \in [m,r)\),再反过来枚举即可。我们找到最大的 \(j\) 满足 \(v_i > v_j\),此时答案就加上 \(v_i \times(\sum_{k=m}^{j}x_k-x_i)\),因为 \(k \in [m, r)\),所以 \(x_k \le x_i\),于是就变成了 \(v_i \times(\sum_{k=m}^{j}x_k -(j - m+1) \times x_i)\),这个 \(\sum_{k=m}^j\) 可以递推,所以每次可以 \(0(1)\) 求出,而 \(j\) 最多移动 \(\frac{n}{2}\) 次,于是这是 \(O(n)\) 的。

我们现在求出了所有情况中 \(v_i(i \in[l,m))\) 更大的,再求所有 \(v_j\) 更大的即可。

时间复杂度是 \(O(n \log n)\)。

代码:提交记录


P5502 [JSOI2015]最大公约数

题目链接:P5502 [JSOI2015]最大公约数

思路:

这道题直接枚举时间复杂度是 \(O(n^2)\)(不过求 \(\gcd\) 还要加上一个 \(\log\)),我们考虑如何用分治去求解。

我们依然是先处理出 \([l,m),[m,r)\) 区间内的答案,然后我们枚举区间的左端点再 \([l,m)\) 内,右端点再 \([m,r)\) 内的权值的最大值。最大公约数可以递推,我们用 \(O(n)\) 的复杂度先预处理出 \(s_i,i \in[l,m)\) 表示 \([i,m)\) 的最大公约数,\(s_j,j \in[m,r)\) 表示 \([m,j]\) 的最大公约数。

最大公约数的个数其实不会超过 \(\log n\) 个,为啥?因为数越多,最大公约数会单调不升,而每次减少至少一半,所以会有 \(s_j\) 的最大公约数相同,且是连续的一段,我们枚举 \(i\),然后找到 \(s_j\) 中所有不同最大公约数中最后一个,去更新答案即可。

时间复杂度是 \(O(n \log^2 n)\)。

代码:提交记录


P8600 [蓝桥杯 2013 省 B] 连号区间数

题目链接:P8600 [蓝桥杯 2013 省 B] 连号区间数

思路:

这题我们不难发现对于一个区间 \([l,r]\),\(\max_{i=l}^rp_i-\min_{i=l}^rp_i=r-l+1\) 如果成立,则这个区间就是一个所谓的连号区间。我们依然是分治,在合并时我们分别处理最大值最小值在左右区间的情况,共四种。每一种都用双指针维护即可。

代码:提交记录


P7883 平面最近点对(加强加强版)

题目链接:P7883 平面最近点对(加强加强版)

思路:

我们首先把所有点按照 \(x\) 轴坐标从小到大排序,分治求出左右两个小区间的最近距离,然后考虑如何求出大区间的。

我们设两个小区间内部最近的两个点最小的距离为 \(d\),同时对两个小区间内的点按 \(y\) 轴从小到大排序,然后再对大区间排序。我们选择 \(x\) 轴坐标排序后在中间的那个点,以它的 \(x\) 轴坐标 \(x_1\) 话一条直线 \(x = x_1\)。我们把所有距离这条直线距离小于 \(d\) 的所有点找出来。因为我们这时是按 \(y\) 轴从小到大排序的,所以选出来的每个点,往后找所有选出的点他们 \(y\) 轴坐标的差的绝对值小于 \(d\) 的,然后跟新答案即可。

这样合并的时间复杂度是 \(O(kn)\) 的,\(k\) 是一个常数,即每个选出的点往后最多有多少个选出的点,他们的 \(y\) 轴坐标差小于 \(d\),一般来说 \(k\le6\),所以可以看成是 \(O(n)\) 的,总的时间复杂度是 \(O(n \log n)\) 的。

代码:提交记录


P3810 【模板】三维偏序(陌上花开)

题目链接:P3810 【模板】三维偏序(陌上花开)

思路:

这题我们可以用 CDQ 分治来解决。我们首先按照 \(a_i\) 来从小到大排序,然后再按照 \(b\) 从小到大归并排序,在归并的同时,我们来处理答案。

我们在处理完两个小区间的答案后,我们枚举有区间每个元素,维护一个指针指向左区间第一个比有区间当前元素的 \(b\) 大的元素,在移动指针的过程中,如过当前的 \(b\) 比较小,就指针移动,同时用树状数组来维护 \(c\),在 \(c\) 上对应的值加一,这样每次合并就是 \(O(n \log n)\) 的了,总的时间复杂度为 \(O(n \log^2 n)\)。

代码:提交记录

标签:int,复杂度,分治,笔记,学习,区间,排序,我们,逆序
From: https://www.cnblogs.com/rlc202204/p/17009454.html

相关文章

  • LCA 学习笔记
    概念LCALCA(LowestCommonAncestor),即最近公共祖先,指的是一棵树中任意两个节点深度最大的共同祖先。有啥用树有一个性质,两点之间有且只有一条简单路径,如果我们把1......
  • ST表学习笔记
    RMQ问题RMQ(RangeMinimum/MaximumQuery)问题是指多次查询某个范围内的最大最小值(或极值),比如对一个序列多次查询区间的最大最小值。设范围内共有\(n\)个元素,查询\(m\)......
  • 最小生成树学习笔记
    基本概念树定义:树是一个连通且无环的简单无向图。一个\(n\)树有以下三个特点:联通。无环。\(n-1\)条边。上面任意两个条件满足都可以得出这个图是一个......
  • 深度学习中的激励函数
    今天我们会来聊聊现代神经网络中必不可少的一个组成部分,激励函数,activationfunction.非线性方程我们为什么要使用激励函数?用简单的语句来概括.就是因为,现实并......
  • 斯坦福 吴恩达 机器学习课程 视频+讲义+课件等 pdf
    非常优质的机器学习入门课程,课程内容有一定难度,但坚持学完,相信一定会有所收获。 关注公众号:后厂村搬砖工。发送:学习资料汇总即可    ......
  • Spring学习笔记 - 第三章 - AOP与Spring事务
    原文地址:Spring学习笔记-第三章-AOP与Spring事务Spring学习笔记全系列传送门:Spring学习笔记-第一章-IoC(控制反转)、IoC容器、Bean的实例化与生命周期、DI(依......
  • SSM框架视频笔记
    Date:2022-12-2800:35:29【尚硅谷】SSM框架全套教程,MyBatis+Spring+SpringMVC+SSM整合一套通关半夜学习的我像个小丑......
  • MMClassification 实践笔记
    1.配置环境参考文档:https://mmclassification.readthedocs.io/zh_CN/dev-1.x/get_started.htmlgitclone-b1.xhttps://github.com/open-mmlab/mmclassification.git......
  • Python学习笔记--高阶技巧
    闭包(避免全局变量被修改的风险)函数的嵌套的利用若是只是调用到外部函数的值,只需要用到函数的嵌套,具体实现如下:若是要对外部函数的值进行修改,需要用到nonlocal关键字,具......
  • Python学习笔记--数据输出
    数据输出输出为Python对象collect算子具体实现:reduce算子具体实现:take算子具体实现:count算子具体实现:输出到文件中saveAsTextFile算子具体实现:起初......