倍增算法

2023-05-16

倍增算法

给定一个整数 M,对于任意一个整数集合 S,定义“校验值”如下:
从集合 S 中取出 M 对数(即 2∗M 个数,不能重复使用集合中的数,如果 S 中的整数不够 M 对,则取到不能取为止),使得“每对数的差的平方”之和最大,这个最大值就称为集合 S 的“校验值”。
现在给定一个长度为 N 的数列 A 以及一个整数 T。
我们要把 A 分成若干段,使得每一段的“校验值”都不超过 T。
求最少需要分成几段。

看见“使得平方之和最大”、“最少需要分成几段”就能发现该题是求解最大最小问题,最开始想到的就应该是二分查找,二分查找是解决最大最小问题最常用的办法。
对于差的平方之和最大,我们可以证明,将最大与最小做差的平方+次大次小做差的平方+···,此时的差的平方之和最大,可以用多项式展开来证明。并且M对数的数量是不定的,因此排序算法是不可避免的。
综上,我们应该用二分来确定每一段的长度,若分成n段则最坏时间复杂度在 O ( n ) O(n) O(n),每段又要调用 O ( l o g n ) O(logn) O(logn)次二分,在每次二分又要用排序算法进行求解 O ( n l o g n ) O(nlogn) O(nlogn)。所以总的算法复杂度在 O ( n 2 l o g 2 n ) O(n^{2}log^{2}n) O(n2log2n),但经过严谨的数学证明应该是 O ( n 2 l o g n ) O(n^{2}logn) O(n2logn)

除了二分以外是否有更好的方法来减低时间复杂度,解决这个问题呢?那就是“倍增”,考虑如下问题

对于给定一个正整数数列,已知其前缀和,如何快速求出从哪位元素开始,之前的所有元素总和大于sum。

这题我们就可以用前缀和的思想来解决,常规方法可能是用二分,但一定会耗费 l o g n logn logn的时间。如果用朴素算法,则更需要 n n n的时间。如果我们在朴素算法上进行改进,遍历前缀和的时候步长是动态变化的,而不是固定为1,遵循以下规则:

  1. end = 0,步长len初始化为1
  2. 如果此时end + l所指代的前缀和小于sum,那么我们就将l乘以2,扩大结尾end = end + l
  3. 如果此时end + l所指代的前缀和大于等于sum,那么我们就将l除以2
  4. 如果l == 0,那么此时就收敛到了结果

相比于二分查找的好处就是,倍增在最差的情况下也才需要 O ( l o g n ) O(logn) O(logn)的时间,而假如所求解越靠近队首,求解时间越短,平均时间复杂度要优于二分。

因此我们考虑用倍增来进一步优化求解,如果使用倍增,那么我们的时间复杂度可以降低为 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n),表示成如下图:
在这里插入图片描述
如果最终结果要分成3段,长度分别是len1、len2和len3,那么在每一段,我们倍增查找用时就是 O ( l o g l e n 1 ) O(loglen1) O(loglen1),排序用时就是 O ( l e n 1 l o g l e n 1 ) O(len1loglen1) O(len1loglen1),因此每一段我们总用时就是 O ( l e n 1 l o g 2 l e n 1 ) O(len1log^{2}len1) O(len1log2len1)。再将这若干段相加可以得到总的时间复杂度 O ( n l o g 2 n ) O(nlog^{2}n) O(nlog2n),相比于二分要降低很多。

接下来,我们考虑进一步的优化。在上面的方法中,我们每倍增一段,就要重新计算整个区间的排序,可不可以利用归并排序的方法,当倍增一段,我们只计算倍增这一区间的排序,然后在利用之前的计算结果归并,这样的话就省去重复计算。
我们来看一下这样的时间复杂度,仍然以上图为例,假设最终结果分成3段,每一段排序的用时就是 O ( l e n 1 l o g l e n 1 ) O(len1loglen1) O(len1loglen1),那么归并用时需要多久呢?我们知道每一段要进行 O ( l o g l e n 1 ) O(loglen1) O(loglen1)的时间,在这些时间里,我们要进行长度为1、2、4、8、······、2len1长度的归并排序,因此归并用时就是这些长度的累加和,利用等比数列求和可知结果是 O ( l e n 1 ) O(len1) O(len1)的,所以每一段的用时就是 O ( l e n l o g l e n + l e n ) O(lenloglen + len) O(lenloglen+len),总用时就是各段用时相加,依旧是 O ( n l o g n + n ) O(nlogn + n) O(nlogn+n)。至此,我们明确了该题的最优解法。

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 500010;

//n表示集合元素个数,t表示校验值,a、b、temp用来做排序运算,m是对数,res是结果
ll n, t, a[N], b[N], temp[N];
int m, res;

ll check(int start, int end, int test){
	//start表示当前要求解段的起始位置,end是当前小于校验值的结尾,test是要尝试的结尾
	//因为我们要对a数组进行排序,所以a数组end到test的顺序有可能之前已经被打乱,要拷贝b数组(原始数据)的内容
    for(int i=end + 1; i<=test; i++) a[i] = b[i];
    sort(a + end + 1, a + test + 1);
    //双指针归并
    int f = start, b = end+1;
    //sum是最终这一段的校验值
    ll sum = 0;
    for(int i=start; i<=test; i++){
        if(b > test || f <= end && a[f]<a[b]) temp[i] = a[f++];
        else temp[i] = a[b++];
    }
    
    for(int i=0; i<(test-start+1>>1) && i<m; i++){
        sum += (temp[start+i]-temp[test-i]) * (temp[start+i]-temp[test-i]);
    }
    return sum;
}

void solve(){
	//start表示当前要求解段的起始位置,end是当前小于校验值的结尾,l是倍增长度
    int start = 0, end = 0, l;
    while(start < n){
        l = 1;
        //如果倍增长度等于0,即收敛
        while(l){
            if(end + l >= n || check(start, end, end + l) > t) l >>= 1;
            else{
            	// 当前end+l结尾可以满足条件,那么我们就将之前排好的序保存下来,方便下次使用
                for(int i=start; i<=end+l; i++) a[i]=temp[i];
                end = end + l;
                l <<= 1;
            }
        }
        //开始下一段的寻找
        start = ++end;
        res++;
    }
}

int main(){
	//k表示测试集组数
    int k;
    cin >> k;
    while(k--){
        cin >> n >> m >> t;
        for(int i=0; i<n; i++){
            cin >> a[i];
            b[i] = a[i];
        }
        res = 0;
        solve();
        cout << res << endl;
    }
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

倍增算法 的相关文章

随机推荐

  • Qt 下载图片并显示图片

    源码下载 xff1a 图片下载器 include 34 mainwindow h 34 include 34 ui mainwindow h 34 include lt QHostAddress gt include lt QDebug g
  • 海康威视 web3.0开发 常见错误 404,403

    海康威视 web3 0开发 常见错误 404 xff0c 403 配置情况 IE 浏览器 43 nginx 43 thinkPHP5 0 43 海康威视200万星光级红外球机1080P变焦云台球机DS 2DC4223IW D 关于如何使用网
  • 虚拟USB设备总结

    开发环境 xff1a windows 首先来总结最近研究的虚拟USB设备 xff0c 进而虚拟USB键盘成功了 xff0c 开心 xff01 得出了一个C S框架 xff0c 首先说一下客户端 客户端有两个部分 xff0c 用户空间工具和底
  • C#Winform:《DataGridViewComboBoxCell值无效》解决方案

    值无效 xff0c 可能是你下拉框选项 xff0c 没有这样的值 xff0c 而你却设置这个值 dataGridView1 Rows i Cells 1 Value 61 Hello World 解决方法就是在窗体的构造函数里添加如下代码
  • FFmpeg笔记

    1 下载 xff0c 配置 FFmpeg官网 xff1a https ffmpeg org 用的系统是Ubuntu18 04 所以直接apt get就可以了 sudo apt get install ffmpeg 2 简介 xff0c 上手
  • 《WPF中TextBox绑定Double类型数据,文本框不能输入小数点》解决方案

    在App cs文件里面 xff0c 重写OnStatup xff0c 添加下面一条语句即可 span class token keyword public span span class token keyword partial span
  • stm32 HAL库串口收发-中断接收DMA发送不定长数据

    使用的时候发现 xff1a 接收完一个字节立即用DMA的方式发送出去 xff0c 会出现数据的丢失 xff0c 如用串口调试助手发送1234 xff0c 返回的只有13 目前只能用缓存buf 43 协议结束 xff08 如0x0d 0x0a
  • headers Authorization

    var auth 61 96 host user host pass 96 const buf 61 Buffer from auth 39 ascii 39 strauth 61 buf toString 39 base64 39 con
  • 平衡车入门---MPU6050陀螺仪的使用

    平衡车入门 MPU6050陀螺仪的使用 一 MPU6050简介二 学习MPU6050的步骤三 I2C协议简介四 MPU6050硬件介绍五 MPU6050的几个重要寄存器六 原始数据的单位换算七 角度换算 滤波算法 一 MPU6050简介 M
  • C++ 为什么基类的析构函数要声明为虚函数

    1 为什么声明基类析构函数为虚函数 xff1f xff08 1 xff09 基类指针 指向 基类对象 xff1a 不用考虑基类析构函数是否声明为虚函数 xff08 2 xff09 基类指针 指向 派生类对象 xff1a 若基类析构函数不为虚
  • std::map find和count效率测试

    1 简介 在使用标准模板库中的map容器且遇到键值对的值为自定义struct或class类型时 xff0c 考虑到特殊场景 xff08 即不能确保key自始至终唯一 xff09 xff0c 若插入新元素 xff08 new 对象 xff09
  • 随机生成8位长字符串(大小写字母及数字组合)

    1 简要说明 项目上开发要用到随机生成一个8位长的字符串 xff08 类似Java工具类中的UUID xff09 xff0c 作为id来对同一事物的不同个体进行唯一标识 xff0c 如同一个班级里学生名字几乎不同 xff0c 偶尔会有重复
  • C++引用和指针区别

    1 C 43 43 引用和指针区别 xff1a 指针是一个新的变量 xff0c 指向另一个变量的地址 xff0c 我们可以通过访问这个地址来修改另一个变量 xff1b 而引用是一个别名 xff0c 对引用的操作就是对变量的本身进行操作指针可
  • TCP/UDP端口号

    大家好呀 xff0c 我是请假君 xff0c 今天又来和大家一起学习数通了 xff0c 今天要分享的知识是TCP UDP端口号 在IP网络中 xff0c 一个IP地址可以唯一地标识一个主机 但一个主机上却可能同时有多个程序访问网络 要标识这
  • C/C++ 电脑微信dat文件解密及工具分享

    1 前言 最近想整理下照片 xff08 回忆 怀旧 xff09 xff0c 以前也知道在微信pc端聊天时 xff0c 图片 视频 文档等文件会缓存在一个目录下 xff08 电脑微信 左下角三条杠 设置 文件管理 xff09 xff0c 点击
  • ODBC::SQLExecDirect返回-1 错误信息ORA-00604 ORA-01000

    在通过使用微软提供的ODBC SDK读取数据库 xff08 SELECT xff09 时 xff0c 发现Oracle读着读着就读不到数据了 xff08 MySQL和SQL Server是正常的 xff09 xff0c 经调试发现SQLEx
  • std::vector与deque首尾增删及遍历(应用于CListCtrl虚拟列表)混合性能测试

    1 简介 在工作项目中应用MFC类库的CListCtrl刷新加载数据 xff0c 一开始是用InsertItem SetItemText 和DeleteItem 等成员函数来实现数据在列表视图控件中的新增和删除 xff08 最多显示500条
  • Matlab 实用代码集

    本博客将存放一些常用的Matlab代码片段 xff0c 整理成博客 xff0c 并持续更新 xff0c 以便写代码可以调用 1 函数多输入多输出 Matlab写函数的时候 xff0c 输入输出个数经常是不固定的 xff0c narginch
  • 基于c++的A-star算法

    资料 xff1a https www cnblogs com guxuanqing p 9610780 html 一 基础原理 1 从起点开始 xff0c 向周围八个方向扩展 测试新扩展的点的路径代价 xff0c 路径代价由已走的路径和距离
  • 倍增算法

    倍增算法 给定一个整数 M xff0c 对于任意一个整数集合 S xff0c 定义 校验值 如下 从集合 S 中取出 M 对数 即 2 M 个数 xff0c 不能重复使用集合中的数 xff0c 如果 S 中的整数不够 M 对 xff0c 则