算法-分治策略

news2024/11/17 23:55:04

概念

分治算法(Divide and Conquer)是一种解决问题的策略,它将一个问题分解成若干个规模较小的相同问题,然后递归地解决这些子问题,最后合并子问题的解得到原问题的解。分治算法的基本思想是将复杂问题分解成若干个较简单的子问题,然后逐个解决这些子问题,最后将子问题的解合并得到原问题的解。

分治算法的基本步骤如下:

  1. 分解(Divide):将原问题分解成若干个规模较小的相同问题。这些子问题应该是相互独立的,即解决一个子问题不会影响其他子问题的解。

  2. 解决(Conquer):递归地解决这些子问题。如果子问题的规模足够小,可以直接求解。否则,继续分解子问题,直到子问题可以直接求解。

  3. 合并(Combine):将子问题的解合并得到原问题的解。这个过程可能涉及到一些计算,但通常比直接解决原问题所需的计算量要少。

分治算法的优点:

  1. 解决问题的原则是将大问题分解成小问题,降低了问题的复杂度。
  2. 利用递归的方式解决问题,使得算法具有很好的可读性和可维护性。
  3. 通过合并子问题的解,可以避免重复计算,提高算法的效率。

分治算法的缺点:

  1. 分治算法的效率往往受限于递归的深度和每层递归的开销。在某些情况下,分治算法的效率可能不如其他算法。
  2. 分治算法的递归调用可能导致栈空间的消耗较大,尤其是在递归深度较大的情况下。

分治算法在计算机科学中应用广泛,例如:

  1. 快速排序、归并排序:这两种排序算法都采用了分治策略,将待排序的序列分成两部分,分别进行排序,然后将排序后的两部分合并成一个有序序列。
  2. 大整数乘法:分治算法可以用于加速大整数的乘法运算,例如Karatsuba算法。
  3. 欧几里得算法:用于求解两个整数的最大公约数,采用分治策略,将较大数分解为若干个较小的数,然后递归地求解这些数的最大公约数,最后合并得到原问题的解。
  4. 循环卷积:分治算法可以用于加速卷积运算,例如快速傅里叶变换(FFT)算法。

总之,分治算法是一种强大的解决问题的策略,通过将复杂问题分解成若干个较简单的子问题,可以降低问题的复杂度,提高算法的效率。

下面本文将介绍几种常见的分治算法应用。

归并排序

【模板】排序

题目描述

将读入的 N N N 个数从小到大排序后输出。

输入格式

第一行为一个正整数 N N N

第二行包含 N N N 个空格隔开的正整数 a i a_i ai,为你需要进行排序的数。

输出格式

将给定的 N N N 个数从小到大输出,数之间空格隔开,行末换行且无空格。

样例 #1

样例输入 #1

5
4 2 4 5 1

样例输出 #1

1 2 4 4 5

提示

对于 20 % 20\% 20% 的数据,有 1 ≤ N ≤ 1 0 3 1 \leq N \leq 10^3 1N103

对于 100 % 100\% 100% 的数据,有 1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^5 1N105 1 ≤ a i ≤ 1 0 9 1 \le a_i \le 10^9 1ai109

分治算法(Divide and Conquer)

分治算法是一种算法设计范式,它通过将问题分解成多个小问题来解决,然后递归地解决这些小问题,最后将这些小问题的解合并起来得到原问题的解。分治算法通常包括三个步骤:

  1. 分解(Divide):将原问题分解成若干个规模较小但形式相同的子问题。
  2. 解决(Conquer):递归地解决这些子问题。如果子问题的规模足够小,则直接解决。
  3. 合并(Combine):将子问题的解合并,以形成原问题的解。

归并排序的分治策略

归并排序是分治算法的一个经典例子。以下是归并排序的分治过程:

  1. 分解:将数组分成两半。这是通过找到数组的中间位置来实现的。

  2. 解决:递归地对这两半进行归并排序。这个过程会一直递归进行,直到每个子数组只有一个元素,这时子数组自然就是有序的。

  3. 合并:将排序好的两半合并成一个有序的数组。这是通过比较两个子数组的前端元素,并将较小的元素放入结果数组中,直到所有元素都被合并。

在这里插入图片描述

图解归并排序

根据图片,我们可以看到归并排序的递归分解过程:

  • 最初的数组是 ( [8, 4, 5, 7, 1, 3, 6, 1, 2] )。
  • 归并排序首先将数组分解为两半:( [8, 4, 5] ) 和 ( [7, 1, 3, 6, 1, 2] )。
  • 然后,每一半再次分解,直到每个子数组只有一个元素。
  • 接着,递归地合并这些子数组,直到最终得到一个完全排序的数组。

时间复杂度

归并排序的时间复杂度是 ( O(n \log n) ),其中 ( n ) 是数组的长度。这是因为:

  • 分解:分解一个大小为 ( n ) 的数组需要 ( O(\log n) ) 层。
  • 合并:每一层的合并操作需要 ( O(n) ) 的时间,因为需要遍历整个数组来合并两个子数组。
  • 因此,总的时间复杂度是每层的 ( O(n) ) 乘以层数 ( O(\log n) ),得到 ( O(n \log n) )。

代码如下所示:

#include<bits/stdc++.h>
using namespace std;
int merged[10000],n,a[10001];

void merge(int l1,int r1,int l2,int r2){
    int i = l1,j = l2,cnt = 0;
    while(i <= r1 && j <= r2){
        if(a[i] < a[j]){
            merged[cnt++] = a[i++];
        }else{
            merged[cnt++] = a[j++];
        }
    }
    while(i <= r1){
        merged[cnt++] = a[i++];
    }
    while(j <= r2){
        merged[cnt++] = a[j++];
    }
    for(int t = 0;t < cnt;t ++) a[l1 + t] = merged[t];
}

void merge_sort(int l,int r){
    if(l < r){
        int mid = (l+r)/2;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        merge(l,mid,mid+1,r);
    }
}

int main(){
    cin >> n;
    for(int i = 0;i < n;i ++) cin >> a[i];
    merge_sort(0,n-1);
    for(int i = 0;i < n;i ++){
        cout << a[i];
        if(i != n-1) cout << " ";
    }
}

最大子段和

题目描述

给出一个长度为 n n n 的序列 a a a,选出其中连续且非空的一段使得这段和最大。

输入格式

第一行是一个整数,表示序列的长度 n n n

第二行有 n n n 个整数,第 i i i 个整数表示序列的第 i i i 个数字 a i a_i ai

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

7
2 -4 3 -1 2 -4 3

样例输出 #1

4

提示

样例 1 解释

选取 [ 3 , 5 ] [3, 5] [3,5] 子段 { 3 , − 1 , 2 } \{3, -1, 2\} {3,1,2},其和为 4 4 4

数据规模与约定
  • 对于 40 % 40\% 40% 的数据,保证 n ≤ 2 × 1 0 3 n \leq 2 \times 10^3 n2×103
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 2 × 1 0 5 1 \leq n \leq 2 \times 10^5 1n2×105 − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 104ai104

题解

分治法的思路是这样的,其实也是分类讨论。

连续子序列的最大和主要由这三部分子区间里元素的最大和得到:

  • 第 1 部分:子区间 [left, mid];
  • 第 2 部分:子区间 [mid + 1, right];
  • 第 3 部分:包含子区间 [mid , mid + 1] 的子区间,即 [mid] 与 [mid + 1] 一定会被选取。
    对这三个部分求最大值即可。

说明:考虑第 3 部分跨越两个区间的连续子数组的时候,由于 [mid] 与 [mid + 1] 一定会被选取,可以从中间向两边扩散,扩散到底 选出最大值。

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6;
int a[N];

int  crossMid(int low,int mid,int high){
	int leftSum = INT32_MIN,sum = 0;
	for(int i = mid;i >= low;i --){
		sum += a[i];
		if(sum > leftSum) leftSum = sum;
	}
	int rightSum = INT32_MIN;
	sum = 0;
	for(int i = mid+1;i <= high;i ++){
		sum += a[i];
		if(sum > rightSum) rightSum = sum;
	}
	return leftSum+rightSum;
}

int maxSubArray(int low,int high){
	if(low == high) return a[low];
	int mid = (low+high)/2;
	int leftSum = maxSubArray(low,mid);
	int rightSum = maxSubArray(mid+1,high);
	int midSum = crossMid(low,mid,high);
	int tmp = max(leftSum,rightSum);
	return max(tmp,midSum);
}

int main(){
	int n;
	cin >> n;
	for(int i = 0;i < n;i ++) cin >> a[i];
	cout << maxSubArray(0,n-1);
	return 0;
}

LCR 170. 交易逆序对的总数

在股票交易中,如果前一天的股价高于后一天的股价,则可以认为存在一个「交易逆序对」。请设计一个程序,输入一段时间内的股票交易记录 record,返回其中存在的「交易逆序对」总数。

示例 1:

输入:record = [9, 7, 5, 4, 6]

输出:8

解释:交易中的逆序对为 (9, 7), (9, 5), (9, 4), (9, 6), (7, 5), (7, 4), (7, 6), (5, 4)。

限制:

0 <= record.length <= 50000

同类问题:

逆序对

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j i<j i<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

题解

逆序对统计:

  1. 终止条件:当 ( l >=r ) 时,代表子数组长度为 1,此时终止划分。
  2. 递归划分:计算数组中点 ( m ),递归划分左子数组 reverseCount(l, m) 和右子数组 reverseCount(m + 1, r)
  3. 合并与逆序对统计
    a. 暂存数组 a 闭区间 ([l, r]) 内的元素至辅助数组 tmp
    b. 循环合并:设置双指针 ij 分别指向左/右子数组的首元素;
    • 当 ( i = m + 1 ) 时:代表左子数组已合并完,因此添加右子数组当前元素 tmp[j],并执行 ( j = j + 1 );
    • 否则,当 ( j = r + 1 ) 时:代表右子数组已合并完,因此添加左子数组当前元素 tmp[i],并执行 ( i = i + 1 );
    • 否则,当 tmp[i]tmp[j] 时:添加左子数组当前元素 tmp[i],并执行 ( i = i + 1 );
    • 否则(即 tmp[i] > tmp[j])时:添加右子数组当前元素 tmp[j] 并执行 ( j = j + 1 );此时构成 ( m - i + 1 ) 个「逆序对」,统计添加至 res
  4. 返回值:返回直至目前的逆序对总数 res
#include <iostream>
#include <cstdio>
using namespace std;
const int MAXN = 5010000;
int n, a[MAXN], tmp[MAXN];

long long reverseCount(int l, int r) {
    if (l >= r) return 0;
    int m = (l + r) / 2;
    long long res = reverseCount(l, m) + reverseCount(m + 1, r);
    int i = l, j = m + 1;
    for (int k = l; k <= r; k++) tmp[k] = a[k];
    for (int k = l; k <= r; k++) {
        if (i > m) { // 左子数组已完全复制
            a[k] = tmp[j++];
        } else if (j > r || tmp[i] <= tmp[j]) {
            a[k] = tmp[i++];
        } else {
            a[k] = tmp[j++];
            res += (m - i + 1); // 计算逆序对数量
        }
    }
    return res;
}

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> a[i];
    printf("%lld\n", reverseCount(0, n - 1));
    return 0;
}

进阶问题

2884. 逆序对

暑假到了,小可可和伙伴们来到海边度假,距离海滩不远的地方有个小岛,叫做欢乐岛,整个岛是一个大游乐园,里面有很多很好玩的益智游戏。

碰巧岛上正在举行“解谜题赢取免费门票”的活动,只要猜出来迷题,那么小可可和他的朋友就能在欢乐岛上免费游玩两天。

迷题是这样的:给出一串全部是正整数的数字,这些正整数都在一个范围内选取,谁能最快求出这串数字中“逆序对”的个数,那么大奖就是他的啦!

当然、主办方不可能就这么简单的让迷题被解开,数字串都是被处理过的,一部分数字被故意隐藏起来,这些数字均用 −1
来代替,想要获得大奖就必须求出被处理的数字串中最少能有多少个逆序对。

小可可很想获得免费游玩游乐园的机会,你能帮助他吗?

注:“逆序对”就是如果有两个数 A 和 B,A 在 B 左边且 A 大于 B,我们就称这两个数为一个“逆序对”。

例如: 4 2 1 3 3 里面包含了 5 个逆序对:(4,2)、(4,1)、(4,3)、(4,3)、(2,1)。

假设这串数字由 5 个正整数组成,其中任一数字 N 均在 1∼4 之间,数字串中一部分数字被 −1 替代后,如:4 2 -1 -1 3,那么这串数字,可能是 4 2 1 3 3,也可能是 4 2 4 4 3,也可能是别的样子。

你要做的就是根据已知的数字,推断出这串数字里最少能有多少个逆序对。

输入格式

第一行两个正整数 N 和 K。

第二行 N 个整数,每个都是 −1 或是一个在 1∼K之间的数。

输出格式

一个正整数,即这些数字里最少的逆序对个数。

数据范围

1≤N≤10000
,
1≤K≤100

输入样例:

5 4
4 2 -1 -1 3

输出样例:

4

#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int dp[N][110],n,a[N],ans,k;
int t1[110],t2[110],cnt;
void add(int tr[],int x,int c){
    for(int i=x;i<=k;i+=i&-i)tr[i]+=c;
}
int sum(int tr[],int x){
    int ans=0;
    for(int i=x;i;i-=i&-i)ans+=tr[i];
    return ans;
}
signed main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        cin>>a[i];
        if(a[i]!=-1)
        add(t1,a[i],1);
    }
    for(int i=n;i>=1;i--){
        if(a[i]!=-1){
            ans+=sum(t2,a[i]-1);
            add(t2,a[i],1);
            add(t1,a[i],-1);
        }
        else{
            cnt++;
            for(int j=1;j<=k;j++)
            dp[cnt][j]=dp[cnt-1][j]+sum(t2,j-1)+sum(t1,k)-sum(t1,j);

            dp[cnt][k+1]=1e9;
            for(int j=k;j>=1;j--)
            dp[cnt][j]=min(dp[cnt][j],dp[cnt][j+1]);
        }

    }
    int mi=1e9;
    for(int i=1;i<=k;i++)mi=min(mi,dp[cnt][i]);
    cout<<ans+mi;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1799741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一文读懂AI时代GPU的内存新宠-HBM

一文读懂GPU最强辅助&#xff1a;HBM HBM&#xff0c;即高带宽内存&#xff0c;是一项领先的3D堆叠DRAM技术&#xff0c;专为高性能计算和图形处理单元&#xff08;GPU&#xff09;设计&#xff0c;满足其对内存带宽和容量的极致需求。该技术由AMD与海力士携手研发&#xff0c;…

eclipse连接后端mysql数据库并且查询

教学视频&#xff1a;https://www.bilibili.com/video/BV1mK4y157kE/?spm_id_from333.337.search-card.all.click&vd_source26e80390f500a7ceea611e29c7bcea38本人eclipse和up主不同的地方如下&#xff0c;右键项目名称->build path->configure build path->Libr…

EasyExcel导出多个sheet封装

导出多个sheet 在需求中&#xff0c;会有需要导出多种sheet的情况&#xff0c;那么这里使用easyexcel进行整合 步骤 1、导入依赖 <dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><d…

IO进程线程(十)进程间通信 消息队列 共享内存 信号灯集

文章目录 一、IPC(Inter-Process Communication)进程间通信相关命令 &#xff1a;&#xff08;一&#xff09;ipcs --- 查看IPC对象&#xff08;二&#xff09;获取IPC键值&#xff08;三&#xff09;删除IPC对象的命令&#xff08;四&#xff09;获取IPC键值的函数1. 函数定义…

CorelDRAW2024最新版本有哪些功能?揭秘设计界最新神器!

“设计”一词最早来源于拉丁语“designare”&#xff0c;意为计划&#xff0c;构思。随着时代的发展&#xff0c;人们将“设计”理解为一种创造性活动&#xff0c;通过这种活动&#xff0c;人们可以创造出新的产品、新的场景以及新的体验。 「CorelDRAW汉化版下载」&#xff0c…

讯方618代表有话说 | 行业大咖详解鸿蒙,全程在线答疑

讯方618“代表有话说” 系列专场直播活动来啦 6月11日&#xff08;周二&#xff09;19:30 本期直播特邀华为、学校、讯方代表 与大家畅聊鸿蒙奥秘 共同开启未来技术之门&#xff01; 行业大咖将带大家 了解鸿蒙概况和岗位需求 解析鸿蒙系统强势崛起带来的影响 解读高校…

Netty中的ByteBuf使用介绍

ByteBuf有三类&#xff1a; 堆缓存区&#xff1a;JVM堆内存分配直接缓冲区&#xff1a;有计算机内存分配&#xff0c;JVM只是保留分配内存的地址信息&#xff0c;相对于堆内存方式较为昂贵&#xff1b;复合缓冲区&#xff1a;复合缓冲区CompositeByteBuf&#xff0c;它为多个B…

【算法专题--栈】最小栈--高频面试题(图文详解,小白一看就会!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐解题方法--1 ⭐解题方法--2 四、总结 五、共勉 一、前言 最小栈这道题&#xff0c;可以说是--栈专题--&#xff0c;比较经典的一道题&#xff0c;也是在面试中频率较高的一道题目&#xff0c;通常在面试中&#xff0c;面试官可…

python - DataFrame查询数据操作

学习目标 掌握获取df一列或多列数据的方法 知道loc和iloc的区别以及使用方法 知道df的query函数的使用方法 知道isin函数的作用和使用方法 获取DataFrame子集的基本方法 1.1 从前从后获取多行数据 案例中用到的数据集在文章顶部 LJdata.csv 前景回顾 head() & tail(…

西门子PLC学习之数据块的单个实例,多重实例与参数实例间的区别

首先介绍下函数&#xff0c;函数块与数据块这三个概念。 数据块 数据块里可以存储各种类型的参数。有人可能会问&#xff0c;m寄存器不是可以存储布尔值&#xff0c;8位&#xff0c;16位&#xff0c;32位变量吗&#xff0c;为什么要多此一举&#xff1f;因为虽然m寄存器能存储以…

超详细的java Comparable,Comparator接口解析

前言 Hello大家好呀&#xff0c;在java中我们常常涉及到对象的比较&#xff0c;不同于基本数据类型&#xff0c;对于我们的自定义对象&#xff0c;需要我们自己去建立比较标准&#xff0c;例如我们自定义一个People类&#xff0c;这个类有name和age两个属性&#xff0c;那么问…

QT creator c动态链接库的创建与调用

QT creator c动态链接库的创建与调用 QT5.15.2 1.创建dll项目 确保两类型选择正确 2.选择MinGW 64-bit 3.点击完成 pro文件参考&#xff1a; QT - guiTEMPLATE lib DEFINES QT_DLL_DEMO_LIBRARYCONFIG c17# You can make your code fail to compile if it uses deprecat…

计算机组成结构—IO系统概述

目录 一、I/O 系统的发展 1. 早期阶段 2. 接口模块和 DMA 阶段 3. 通道结构阶段 4. 处理机阶段 二、I/O 系统的组成 1. I/O 软件 2. I/O 硬件 三、I/O 设备 1. I/O 设备分类 2. I/O 设备的组成 在计算机中&#xff0c;除 CPU 和主存两大模块之外&#xff0c;第三个重…

Vue项目安装axios报错npm error code ERESOLVE npm error ERESOLVE could not resolve解决方法

在Vue项目中安装axios时报错 解决方法&#xff1a;在npm命令后面加--legacy-peer-deps 例如&#xff1a;npm install axios --save --legacy-peer-deps 因为别的需求我把node版本重装到了最新版&#xff08;不知道是不是这个原因&#xff09;&#xff0c;后来在项目中安装axi…

STM32作业实现(四)光敏传感器

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

【Python爬虫单点登录实战】PyExecJS破解慧职教:过河源技术学院单点登录统一身份认证

目录 前言大致分析PyExecJS 使用案例pip 安装:Demo:输出:案例1.访问目标网站的登录页面并查看源码2.将js放到和py脚本同一级目录下3. 编写Python脚本来调用js破解单点登录实战提取密钥参数清洗数据登陆测试单点登录获取ticket获取jsessionid获取token成功我的专栏前言 博主提供…

Python 知识图谱补全,Knowledge Graph Completion,基于大模型的知识图谱补全,基于LLMs的KGC任务

今天讲一篇文章《Exploring Large Language Models for Knowledge Graph Completion》 &#xff0c;这篇文章主题&#xff1a;基于大模型做知识图谱补全 1.文章主要思想&#xff1a; 本章描述知识图谱补全中的三个任务&#xff1a;三元组分类、关系预测和实体(链接)预测&…

微信如何防止被对方拉黑删除?一招教你解决!文末附软件!

你一定不知道&#xff0c;微信可以防止被对方拉黑删除&#xff0c;秒变无敌。只需一招就能解决&#xff01;赶快来学&#xff01;文末有惊喜&#xff01; 惹到某些重要人物&#xff08;比如女朋友&#xff09;&#xff0c;被删除拉黑一条龙&#xff0c;那真的是太令人沮丧了&a…

Ubuntu server 24 (Linux) AdGuard Home +SmartDNS 安装配置 搭建去广告快速DNS

一 SmartDNS 安装 &#xff0c;可参考&#xff1a;Ubuntu server 24 (Linux) 安装部署smartdns 搭建智能DNS服务器-CSDN博客 二 安装AdGuard 1 下载地址&#xff1a;GitHub - AdguardTeam/AdGuardHome: Network-wide ads & trackers blocking DNS server 2 解压安装 #下…

路由器重启真的好吗?多久重启一次更好?

前言 小白前段时间发现自己家的OpenWRT软路由上网特别慢&#xff0c;有时候通话还有点卡顿。 然而有个朋友用的普通路由器也有类似的问题&#xff0c;而且有时候根本上不去网。 解决的办法很简单&#xff1a;重启路由器。 重启路由器&#xff1f; 但路由器重启是真的好吗&a…