倍增与ST算法

news2024/11/13 14:36:55

倍增与ST算法

  • 倍增
    • 倍增原理
    • 倍增法的局限
    • 例题 :国旗计划 (洛谷 P4155)
    • 例题题解
    • 带注释的代码
  • ST算法
    • ST算法原理
    • ST算法步骤
    • ST算法应用场合
    • 例题 :【模板】ST表 (洛谷 P3865)

倍增

倍增原理

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

倍增法的局限

在这里插入图片描述

例题 :国旗计划 (洛谷 P4155)

在这里插入图片描述

例题题解

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

带注释的代码

/*
只需要知道 
go[i, 0] = i 往后跳 2^0 = 1次

可以求
go[i, k] = i 往后跳 2^k 次 

求的过程:
go[i, k] = go[go[i, k - 1], k - 1]
按照k 从小到大求


题目 
我s区间, 求最少多少次跳跃最优区间可以覆盖所有的点
假如跳x次最优区间之后, 它的右端点第一次>=l[s]
那么我的答案就是x

l[s]------>m ---->(>=l[s])

ne[i, 2] = i 往后跳 2^2 次

ne[i - 1, 0] = i
ne[i - 1, 1] = i - 1 + 1 + 2
			   ne[ne[i - 1, 0], 0]

int s;
int startl = l[s]

最终答案x 
x = (1010110)_2
2^6 √
对于我现在的s 
x = (0010110)
2^5 x

2^4 √
2^2 √
x = (0000010)
2^1 x
2^0 √

x = (0000001)

for (int i = 18; i >= 0; i -- ) {
	if(r[go[s][i]] < startl) {
		s = go[s][i];
	}
}

r[s] < startl
r[go[s][0]] >= startl

思路: 
1.分析贪心策略
2.考虑倍增, 预处理go[s, k]
	1. 处理go[s, 0]
	2. 通过go[s, i]->go[s, i + 1]

3. 二进制求值
for (int i = 18; i >= 0; i -- ) {
	if(r[go[s][i]] < startl) {
		s = go[s][i];
	}
}

1 : [1, 5]
2 : [2, 4]
3 : [3, 6]

(1, 2) 不合法的
(1, 3), (2, 3) 合法的

l[i] > l[j]
r[i] > r[j]


ST表
f[i][k] 代表着 [i, i+2^k-1] 区间的最小值
f[i][k] =  min(f[i][k - 1], f[i + 2^(k-1)][k-1])
[i, i+2^k-1] = [i, i+2^{k-1}-1]U[i+2^{k-1}, i+2^k-1]

1.把f[i][0]求出来
2.递推求出f[i][k] 


求[l, r]的最小值 
query(l, r) :
int k = log2(r - l + 1)
return min(f[l, k], f[r - 2^k + 1, k]);
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 5e5 + 10; 

int go[N][20];
// go[i][k] 代表第i个区间往后跳2^k次所在区间 

int main() {
	int n, m;
	std::cin >> n >> m;
	std::vector<array<int, 3>> seg(2 * n, {0, 0, 0}); 
	// 输入数据, 将输入的环破坏了, 成为一个链
	// 如果出现l > r的, 那么我们直接将 r += m 
	// 也就是说将[1, m] -> [1, 2 * m], [m + 1, 2 * m]部分也同样用于表示[1, m] 
	// 那么我如果是以s作为开始的区间, 那么我只要去寻找r >= l[s] + m的最小覆盖区间即可 
	for (int i = 0; i < n; i ++ ) {
		int l, r;
		std::cin >> l >> r;
		seg[i] = {l, r, i};
		if(l > r) {
			seg[i][1] += m;
		}
	}
	
	for (int i = n; i < 2 * n; i ++ ) {
		seg[i] = seg[i - n];
		seg[i][0] += m;
		seg[i][1] += m;
	} 
	
	// 给区间排序, 按照l, r, id从小到大排序 
	sort(seg.begin(), seg.end());
	
	// 预处理第i个区间的最优区间 
	for (int i = 0, j = 0; i < 2 * n; i ++ ) {
		// 找到最大的l[j] <= r[i] 
		while(j + 1 < 2 * n && seg[j + 1][0] <= seg[i][1]) {
			j ++ ;
		}
		go[i][0] = j;
	}
	
	// 预处理go 
	for (int j = 1; j <= 19; j ++ ) {
		for (int i = 0; i < 2 * n; i ++ ) {
			go[i][j] = go[go[i][j - 1]][j - 1];
		} 
	}
	
	// 求答案 
	std::vector<int> ans(n);
	for (int i = 0; i < n; i ++ ) {
		int l = seg[i][0];
		int r = seg[i][1];
		int id = seg[i][2];
		
		int s = i, bd = l + m; //  开始区间, 最终目标值 
		int ret = 1; //算是初始选的区间 
		for (int j = 19; j >= 0; j -- ) {
			if(seg[go[s][j]][1] < bd) { // 如果没有超过目标 
				s = go[s][j]; // 跳跃 
				ret += (1 << j); // 算上答案  
			}
		}
		ret += 1; // 最后跳一次, 抵达目标 
		ans[id] = ret;
	}
	
	for (int i = 0; i < n; i ++ ) {
		std::cout << ans[i] << " \n"[i == n - 1];
	}
}

这个代码是使用了倍增法处理区间覆盖的问题。这种问题的关键在于找到覆盖目标区间需要的最少的跳跃次数。这个代码的基本思路是:

  1. 把环状的边防站变为线状,这样处理起来更方便。如果一个区间的左边界大于右边界,那么这个区间实际上是跨过了边界。我们把这个区间的右边界加上总的边防站数量,使其变为一个正常的区间。

  2. 根据区间的左边界和右边界进行排序,使得所有的区间按照左边界从小到大排列。

  3. 预处理每个区间的最优跳跃区间。具体来说,对于第i个区间,我们找到一个最大的j,使得第j个区间的左边界不超过第i个区间的右边界。这个j就是第i个区间的最优跳跃区间。

  4. 通过预处理的最优跳跃区间,我们可以快速地求出每个区间往后跳跃2^k次所在的区间。这样就大大减少了求解的时间复杂度。

  5. 对于每个起始区间,我们找到最少的跳跃次数,使得最后跳跃到的区间的右边界大于等于起始区间的左边界加上总的边防站数量。这个最少的跳跃次数就是我们要求的答案。

这个代码主要使用了贪心的策略,通过预处理的方式大大提高了效率。对于每个起始区间,我们总是尽可能地选择跳跃到最远的区间,直到覆盖了所有的目标点。

ST算法

在这里插入图片描述

ST算法原理

在这里插入图片描述

ST算法步骤

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

ST算法应用场合

在这里插入图片描述

例题 :【模板】ST表 (洛谷 P3865)

题目背景

这是一道 ST 表经典题——静态区间最大值

请注意最大数据时限只有 0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O ( 1 ) O(1) O(1)。若使用更高时间复杂度算法不保证能通过。

如果您认为您的代码时间复杂度正确但是 TLE,可以尝试使用快速读入:

inline int read()
{
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

函数返回值为读入的第一个整数。

快速读入作用仅为加快读入,并非强制使用。

题目描述

给定一个长度为 N N N 的数列,和 M M 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 1N,M10

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

对于 100 % 100\% 100% 的数据,满足 1 ≤ N ≤ 10 5 1\le N\le {10}^5 1N105 1 ≤ M ≤ 2 × 10 6 1\le M\le 2\times{10}^6 1M2×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 1liriN

带注释的代码

#include <bits/stdc++.h>
using namespace std;

// 定义最大的序列长度 MAXN 和最大的 log 值 MAXLOG
const int MAXN = 100005, MAXLOG = 20;

// st[i][j] 存储的是从 i 开始,长度为 2^j 的区间的最大值
// Log[i] 存储的是 i 的二进制位数减 1,相当于 log2(i) 的下取整
// a[i] 用来存储输入的序列
int st[MAXN][MAXLOG], Log[MAXN];
int a[MAXN];

// 一个快速读入的函数,用于提高读入效率
inline int read() {
	int x=0,f=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
	while (ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
	return x*f;
}

// 查询函数,接受查询区间的两个边界,返回区间内的最大值
int query(int l, int r) {
	int k = Log[r - l + 1]; // 计算区间长度的 log 值
	// 返回两个重叠的区间的最大值,这两个区间都是最大的可以包含在查询区间内的2的幂长度的区间
	return max(st[l][k], st[r - (1 << k) + 1][k]);
}

int main() {
	// 读取数列的长度 N 和查询的次数 M
	int N = read(), M = read();
	
	// 初始化 Log 数组,预处理出 1 到 N 的每个数的 log 值
	Log[1] = 0;
	for(int i = 2; i <= N; i++)
		Log[i] = Log[i / 2] + 1;
	
	// 读取数列并构造 ST 表
	// st[i][0] 是数列的原始数据
	for(int i = 1; i <= N; i++)
		st[i][0] = read();
	// 构造 ST 表,计算出从每个位置开始的,长度为 2 的幂的每个区间的最大值
	for(int j = 1; j <= 20; j++)
		for(int i = 1; i + (1 << j) - 1 <= N; i++)
			st[i][j] = max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
	
	// 进行 M 次查询,每次查询读取查询区间,然后通过 ST 表查询区间的最大值,并输出结果
	while(M--) {
		int l = read(), r = read();
		printf("%d\n", query(l, r));
	}
	return 0;
}

这个代码主要是利用了 ST 表(Sparse Table,稀疏表)的性质来求区间最大值。它是一种预处理的方法,可以在 O(1) 的时间复杂度内查询到任意区间的最大值,非常适合解决这种区间查询的问题。

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

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

相关文章

华为OD机试真题 Java 实现【报文回路】【2023 B卷 100分】,俗称“礼尚往来”

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路1、报文回路2、异常情况&#xff1a;3、解题思路 五、Java算法源码六、效果展示1、输入2、输出 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&…

《JavaSE-第二十章》之线程的创建与Thread类

文章目录 什么是进程&#xff1f;什么是线程&#xff1f;为什么需要线程&#xff1f; 基本的线程机制创建线程1.实现 Runnable 接口2.继承 Thread 类3.其他变形 Thread常见构造方法1. Thread()2. Thread(Runnable target)3. Thread(String name)4. Thread(Runnable target, Str…

epoll复用

cli #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>// 服务器ip #define IP "192.168.250.100" // 服务器端口 #define PORT 8888int main…

c++11 标准模板(STL)(std::basic_ifstream)(一)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_ifstream : public std::basic_istream<CharT, Traits> 类模板 basic_ifstream 实现文件流上的高层输入操作。它将 std::basic_istream…

Flink - souce算子

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 目录 1. 从Java的集合中读取数据 2. 从本地文件中读取数据 3. 从HDFS中读取数据 4. 从Socket中读取数据 5. 从Kafka中读取数据 6. 自定义Source 官方文档 - Flink1.13 1. 从Java的集合中读取数据 …

二叉树(C语言)

文章目录 1.树1.1概念1.2相关定义1.3 表示&#xff08;左孩子右兄弟&#xff09; 2.二叉树2.1概念2.2特殊的二叉树1. 满二叉树&#xff1a;2. 完全二叉树&#xff1a; 2.3二叉树的性质2.4练习 3.二叉树的存储结构1. 顺序存储2. 链式存储 4.完全二叉树的代码实现4.1堆的介绍1.堆…

ssm德宏贸易项目java人资企业办公jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 ssm德宏贸易项目 系统有1权限&#xff1a;管理员 二…

接口自动化测试平台

下载了大神的EasyTest项目demo修改了下<https://testerhome.com/topics/12648 原地址>。也有看另一位大神的HttpRunnerManager<https://github.com/HttpRunner/HttpRunnerManager 原地址>&#xff0c;由于水平有限&#xff0c;感觉有点复杂~~~ 【整整200集】超超超…

查询结果元数据-MetaData对象、数据库工具类的封装、通过反射实现数据查询的封装

六、查询结果元数据-MetaData对象 七、数据库工具类的封装 1、PropertieUtil类 2、DbUtil类 3、DBHepler类 查询&#xff1a; 4、TestDb测试类&#xff1a; 更新&#xff1a; 1&#xff09;插入&#xff1a; 2&#xff09;修改&#xff1a; 3&#xff09;删除&#xff1a; 查…

2024考研408-计算机网络 第二章-物理层学习笔记

文章目录 前言一、通信基础1.1、物理层基本概念1.1.1、认识物理层1.1.2、认识物理层的四种接口特性 1.2、数据通信基础知识1.2.1、典型的数据通信模型及相关术语1.2.2、数据通信相关术语1.2.3、设计数据通信系统要考虑的三个问题&#xff1a;问题1&#xff1a;采用单工通信/半双…

通讯录的实现(超详细)——C语言(进阶)

目录 一、创建联系人信息&#xff08;结构体&#xff09; 二、创建通讯录&#xff08;结构体&#xff09; 三、define定义常量 四、打印通讯录菜单 五、枚举菜单选项 六、初始化通讯录 七、实现通讯的的功能 7.1 增加加联系人 7.2 显示所有联系人的信息 ​7.3 单独查…

【自动化运维】Ansible常见模块的运用

目录 一、Ansible简介二、Ansible安装部署2.1环境准备 三、ansible 命令行模块3.1&#xff0e;command 模块3.2&#xff0e;shell 模块3.3&#xff0e;cron 模块3.4&#xff0e;user 模块3.5&#xff0e;group 模块3.6&#xff0e;copy 模块3.7&#xff0e;file 模块8&#xff…

C++之观察者模式(发布-订阅)

目录 模式简介 介绍 优点 缺点 代码实现 场景说明 实现代码 运行结果 模式简介 观察者模式&#xff08;Observer Pattern&#xff09;&#xff0c;也叫我们熟知的发布-订阅模式。 它是一种行为型模式。 介绍 观察者模式主要关注的是对象的一对多的关系&#xff0c; …

4-3 Working with time series

本文所用数据下载 Data from a Washington, D.C., bike-sharing system reporting the hourly count of rental bikes in 2011–2012 in the Capital Bikeshare system, along with weather and seasonal information. Our goal will be to take a flat, 2D dataset and trans…

搭建网站 --- 快速WordPress个人博客并内网穿透发布到互联网

文章目录 快速WordPress个人博客并内网穿透发布到互联网 快速WordPress个人博客并内网穿透发布到互联网 我们能够通过cpolar完整的搭建起一个属于自己的网站&#xff0c;并且通过cpolar建立的数据隧道&#xff0c;从而让我们存放在本地电脑上的网站&#xff0c;能够为公众互联…

JS——输入输出语法数组的操作

JavaScript输入输出语法 目标&#xff1a;能写出常见的JavaScript输入输出语法 输出语法 语法1&#xff1a; document.write(要输出的内容)作用&#xff1a; 向body内输出内容 注意&#xff1a; 如果输出的内容写的是标签&#xff0c;也会被解析成网页元素 语法2&#xff1a…

2023大同首届信息技术产业峰会举行,共话数字经济新未来

7月28日&#xff0c;“聚势而强共领信创”2023大同首届信息技术产业峰会圆满举行。本次峰会由中共大同市委、大同市人民政府主办&#xff0c;中国高科技产业化研究会国际交流合作中心、山西省信创协会协办&#xff0c;中共大同市云冈区委、大同市云冈区人民政府、诚迈科技&…

一文深入了解Cmk

目录 一、Cmk&#xff08;设备能力指数&#xff09;介绍&#xff1a;二、Cmk&#xff08;设备能力指数&#xff09; 概念&#xff1a;三、Cmk的应用时机&#xff1a;四、Cmk前期准备和要求&#xff1a;五、Cmk测试要求&#xff1a;六、CMK计算公式&#xff1a;七、Cmk实际操作&…

机器学习的关键词和算法总结

随着全球各行业的数据治理、数字化转型智能化辅助的引入发展&#xff0c;机器学习&#xff08;包括深度学习&#xff09;在逐步深入到各行各业&#xff0c;所以&#xff0c;有必要对机器学习的常见术语&#xff0c;经典算法及应用场景进行一次总结&#xff0c;其实机器学习兴起…

Vue2 第九节 过滤器

&#xff08;1&#xff09;定义&#xff1a;对要显示的数据进行特定格式化后再显示 &#xff08;2&#xff09;语法&#xff1a; ① 注册过滤器 1&#xff09;Vue.filter(name, callback) 全局过滤器 2&#xff09; new Vue({filters:{}}) 局部过滤器 ② 使用过滤器 1&…