信奥赛C++提高组csp-s之倍增算法

信奥赛C++提高组csp-s之倍增算法

倍增算法核心思想讲解

1. 什么是倍增?

"倍增",顾名思义,就是成倍地增加。它的核心思想是:不是一步一步地处理问题,而是将每一步的"步长"以2的幂次(1, 2, 4, 8...)进行跳跃式处理

2. 为什么倍增有效?
  • 高效性:通过二进制划分,可以将一个线性过程的时间复杂度从 O(N) 优化到 O(logN)。
  • 可行性 :任何整数都可以用二进制数表示。这意味着,从起点到终点的任意一个长度,我们都可以通过 2^k1 + 2^k2 + ... 的跳跃方式到达。
  • 预处理:倍增算法通常需要先进行预处理,计算出一些"跳跃点"的信息,以便在查询时能够快速组合。
3. 倍增的通用步骤
  1. 定义状态f[i][j] 通常表示从 i 这个点出发,走 2^j 步(或者进行 2^j 次操作)后所到达的状态。这个"状态"可以是到达的位置、区间的最值、区间和等。
  2. 预处理(DP填充) :这是算法的关键。我们利用递推关系来填充这个 f 数组(也称为ST表、DP表)。
    • 边界条件f[i][0] 表示从 i 走 1 (2^0) 步后的状态。这是初始的、已知的数据。
    • 递推公式f[i][j] = f[ f[i][j-1] ][j-1]。这个公式是倍增的灵魂!它的意思是:i2^j 步到达的点,等价于从 i 先走 2^(j-1) 步,到达一个中间点 f[i][j-1],然后再从这个中间点走 2^(j-1)
  3. 查询/执行 :对于一次查询,比如"从点 uk 步会到哪?",我们将 k 分解成二进制。例如 k = 13 = 8 + 4 + 1(二进制 1101)。那么我们就依次走 8 步、4 步、1 步。每一步的跳跃都可以直接从我们预处理好的 f 数组中 O(1) 获取。

案例:ST 表 & RMQ 问题

题目描述

给定一个长度为 N N N 的数列,和 M 次询问,求出每一次询问的区间内数字的最大值。

输入格式

第一行包含两个整数 N , M N,M N,M,分别表示数列的长度和询问的个数。

第二行包含 N N N 个整数(记为 a i a_i ai),依次表示数列的第 i i i 项。

接下来 M M M 行,每行包含两个整数 l i , r i l_i,r_i li,ri,表示查询的区间为 [ l i , r i ] [l_i,r_i] [li,ri]。

输出格式

输出包含 M M M 行,每行一个整数,依次表示每一次询问的结果。

输入输出样例 #1
输入 #1
复制代码
8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8
输出 #1
复制代码
9
9
7
7
9
8
7
9
说明/提示

对于 30 % 30\% 30% 的数据,满足 1 ≤ N , M ≤ 10 1\le N,M\le 10 1≤N,M≤10。

对于 70 % 70\% 70% 的数据,满足 1 ≤ N , M ≤ 10 5 1\le N,M\le {10}^5 1≤N,M≤105。

对于 100 % 100\% 100% 的数据,满足 1 ≤ N ≤ 10 5 1\le N\le {10}^5 1≤N≤105, 1 ≤ M ≤ 2 × 10 6 1\le M\le 2\times{10}^6 1≤M≤2×106, a i ∈ [ 0 , 10 9 ] a_i\in[0,{10}^9] ai∈[0,109], 1 ≤ l i ≤ r i ≤ N 1\le l_i\le r_i\le N 1≤li≤ri≤N。

AC代码

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;  // 定义最大数组长度
int n, m;                // n: 数列长度, m: 询问个数
int a[N];                // 存储原始数列
int st[N][17];           // ST表,st[i][j]表示从位置i开始,长度为2^j的区间最大值

int main() {
    cin >> n >> m;
    
    // 读入数列并初始化ST表第一层
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        st[i][0] = a[i];  // 长度为1的区间最大值就是元素本身
    }
    
    // 构建ST表
    for(int j = 1; j <= 17; j++) {                    // j表示区间长度为2^j
        for(int i = 1; i + (1 << j) - 1 <= n; i++) {  // i为区间起点
            // 将区间[i, i+2^j-1]分成两个长度为2^(j-1)的子区间
            // 取两个子区间最大值的较大者作为当前区间的最大值
            st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
        }
    }
    
    // 处理每个查询
    while(m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        
        // 计算区间长度的对数(向下取整)
        int s = log2(r - l + 1);
        
        // 查询区间最大值:将区间分成可能有重叠的两部分
        // [l, l+2^s-1] 和 [r-2^s+1, r]
        int ans = max(st[l][s], st[r - (1 << s) + 1][s]);
        printf("%d\n", ans);
    }
    
    return 0;
}

功能分析

使用ST表(Sparse Table) 解决区间最大值查询(RMQ)

核心思想:
  • 预处理 :构建一个二维数组st,其中st[i][j]存储从位置i开始,长度为2^j的区间内的最大值
  • 查询 :对于任意区间[l, r],可以将其分解为两个可能有重叠的区间,取这两个区间最大值的较大者
算法步骤:
  1. 初始化

    • st[i][0] = a[i](长度为1的区间最大值就是元素本身)
  2. 构建ST表

    • 使用动态规划思想,st[i][j] = max(st[i][j-1], st[i+2^(j-1)][j-1])
    • 即将大区间分成两个小区间,取两者的最大值
  3. 查询处理

    • 对于区间[l, r],计算s = log2(r-l+1)(区间长度的对数)
    • 查询结果 = max(st[l][s], st[r-2^s+1][s])
复杂度分析:
  • 预处理:O(N log N)
  • 单次查询:O(1)
  • 总复杂度:O(N log N + M)
优势:
  • 查询速度极快(O(1)),适合处理大量查询
  • 代码简洁,实现相对容易
适用场景:
  • 静态数据(数据不修改)
  • 查询次数远大于数据修改次数
  • 需要快速响应大量区间查询

更多系列知识,请查看专栏:《信奥赛C++提高组csp-s知识详解及案例实践》:
https://blog.csdn.net/weixin_66461496/category_13113932.html


各种学习资料,助力大家一站式学习和提升!!!

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"##########  一站式掌握信奥赛知识!  ##########";
	cout<<"#############  冲刺信奥赛拿奖!  #############";
	cout<<"######  课程购买后永久学习,不受限制!   ######";
	return 0;
}

1、csp信奥赛高频考点知识详解及案例实践:

CSP信奥赛C++动态规划:
https://blog.csdn.net/weixin_66461496/category_13096895.html点击跳转

CSP信奥赛C++标准模板库STL:
https://blog.csdn.net/weixin_66461496/category_13108077.html 点击跳转

信奥赛C++提高组csp-s知识详解及案例实践:
https://blog.csdn.net/weixin_66461496/category_13113932.html

2、csp信奥赛冲刺一等奖有效刷题题解:

CSP信奥赛C++初赛及复赛高频考点真题解析(持续更新):https://blog.csdn.net/weixin_66461496/category_12808781.html 点击跳转

CSP信奥赛C++一等奖通关刷题题单及题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12673810.html 点击跳转

3、GESP C++考级真题题解:

GESP(C++ 一级+二级+三级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12858102.html 点击跳转

GESP(C++ 四级+五级+六级)真题题解(持续更新):https://blog.csdn.net/weixin_66461496/category_12869848.html 点击跳转

GESP(C++ 七级+八级)真题题解(持续更新):
https://blog.csdn.net/weixin_66461496/category_13117178.html

4、CSP信奥赛C++竞赛拿奖视频课:

https://edu.csdn.net/course/detail/40437 点击跳转

· 文末祝福 ·

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	cout<<"跟着王老师一起学习信奥赛C++";
	cout<<"    成就更好的自己!       ";
	cout<<"  csp信奥赛一等奖属于你!   ";
	return 0;
}
相关推荐
低频电磁之道2 小时前
编译C++的几种方式(MSVC编译器)
开发语言·c++
Zsy_0510032 小时前
【C++】类和对象(一)
开发语言·c++
是娇娇公主~2 小时前
工厂模式详细讲解
数据库·c++
_OP_CHEN3 小时前
【从零开始的Qt开发指南】(二十三)Qt 界面优化之 QSS 实战指南:从入门到精通,让你的界面颜值飙升!
开发语言·c++·qt·前端开发·界面美化·qss·客户端开发
HellowAmy3 小时前
我的C++规范 - 跳跃的对象
开发语言·c++·代码规范
lucky-billy3 小时前
架构设计 - std::forward 条件转换配合万能引用(T&&)来实现完美转发
c++·完美转发·forward·万能引用
bkspiderx4 小时前
C/C++中float浮点型的存储方式与使用要点
c++
起个名字费劲死了4 小时前
QT + Socket 客户端/服务端 公网通讯
服务器·c++·qt·socket
我是一只小青蛙8885 小时前
位图与布隆过滤器:高效数据结构解析
开发语言·c++·算法