当前位置: 首页 > article >正文

CCF-CSP第24次认证第2题 --《序列查询新解》

4281. 序列查询新解 - AcWing题库

上一题“序列查询”中说道:

A=[A0,A1,A2,⋯,An]A=[A0,A1,A2,⋯,An] 是一个由 n+1n+1 个 [0,N)[0,N) 范围内整数组成的序列,满足 0=A0<A1<A2<⋯<An<N0=A0<A1<A2<⋯<An<N。

基于序列 AA,对于 [0,N)[0,N) 范围内任意的整数 xx,查询 f(x)f(x) 定义为:序列 AA 中小于等于 xx 的整数里最大的数的下标

对于给定的序列 AA 和整数 xx,查询 f(x)f(x) 是一个很经典的问题,可以使用二分搜索在 O(logn)O(log⁡n) 的时间复杂度内轻松解决。

但在 IT 部门讨论如何实现这一功能时,小 P 同学提出了些新的想法。

小 P 同学认为,如果事先知道了序列 AA 中整数的分布情况,就能直接估计出其中小于等于 xx 的最大整数的大致位置。

接着从这一估计位置开始线性查找,锁定 f(x)。

如果估计得足够准确,线性查找的时间开销可能比二分查找算法更小。

比如说,如果 A1,A2,⋯,An 均匀分布在 (0,N)(0,N) 的区间,那么就可以估算出:

f(x)≈(n+1)⋅xN

为了方便计算,小 P 首先定义了比例系数 r=⌊Nn+1⌋,其中 ⌊ ⌋ 表示下取整,即r 等于 N 除以 n+1 的商。

进一步地,小 P 用 g(x)=⌊xr⌋ 表示自己估算出的 f(x)的大小,这里同样使用了下取整来保证 g(x)是一个整数。

显然,对于任意的询问 x∈[0,N),g(x) 和 f(x)越接近则说明小 P 的估计越准确,后续进行线性查找的时间开销也越小。

因此,小 P 用两者差的绝对值 |g(x)−f(x)|来表示处理询问 xx 时的误差。

为了整体评估小 P 同学提出的方法在序列 A 上的表现,试计算:

error(A)=∑i=0N−1|g(i)−f(i)|=|g(0)−f(0)|+⋯+|g(N−1)−f(N−1)|

输入格式

输入的第一行包含空格分隔的两个正整数 n 和 N。

输入的第二行包含 n 个用空格分隔的整数 A1,A2,⋯,An。

注意 A0 固定为 0,因此输入数据中不包括 A0。

输出格式

仅输出一个整数,表示 error(A)的值。

数据范围

70%70% 的测试数据满足 1≤n≤200且 n<N≤1000;
全部的测试数据满足 1≤n≤105 且 n<N≤109

输入样例1:
3 10
2 5 8
输出样例1:
5
样例1解释

A=[0,2,5,8]
r=⌊Nn+1⌋=⌊103+1⌋=2

i0123456789
f(i)0011122233
g(i)g(i)0011223344
∥g(i)−f(i)∥∥g(i)−f(i)∥0000101111
输入样例2:
9 10
1 2 3 4 5 6 7 8 9
输出样例2:
0
输入样例3:
2 10
1 3
输出样例3:
6
样例3解释

A=[0,1,3]
r=⌊Nn+1⌋=⌊102+1⌋=3

i0123456789
f(i)0112222222
g(i)0001112223
∥g(i)−f(i)∥0111110001
提示

需要注意,输入数据 [A1⋯An]并不一定均匀分布在 (0,N) 区间,因此总误差 error(A)可能很大。

题解:

#include <iostream>
#include <vector>
#include <cmath>using namespace std;int main() {int n, N;cin >> n >> N;int r = N / (n + 1); // 计算比例系数 rvector<int> A(n + 1); A[0] = 0; for (int i = 1; i <= n; i++) {cin >> A[i];}long long error = 0;for (int i = 0; i <= n; i++) { // 遍历每个区间int L = A[i]; // 当前区间的左端点int R = (i == n) ? N : A[i + 1]; // 当前区间的右端点,最后一个区间右端点为 Nint f_i = i; // f(i) 在该区间内恒为 iint g_L = L / r; // g(L) = ⌊L / r⌋,即 L 处的 g(i) 值int g_R = (R - 1) / r; // g(R-1) = ⌊(R-1) / r⌋,即 R-1 处的 g(i) 值if (g_L == g_R) { // 如果 g(i) 在整个区间[L, R)内保持不变error += abs(f_i - g_L) * (R - L); // 直接计算整个区间的误差} else { // 如果 g(i) 在区间内有变化for (int g_i = g_L; g_i <= g_R; g_i++) { // 遍历 g(i) 变化的不同区间int seg_L = max(L, g_i * r); // 计算当前 g(i) 段的左端点int seg_R = min(R, (g_i + 1) * r); // 计算当前 g(i) 段的右端点error += abs(f_i - g_i) * (seg_R - seg_L); // 计算该段误差并累加}}}cout << error;return 0;
}

总结:

  • f(i) = i,因为 A[i] ≤ i < A[i+1]f(i) 的值就是 j
  • g(i) = ⌊i / r⌋,在 [L, R) 计算 g(L)g(R-1)
  • g_LL 除以 r 的商,即区间开始时 g(i) 的值。
  • g_R(R - 1) 除以 r 的商,即区间结束时 g(i) 的值。
  • 如果 g_L == g_R,表示在整个区间内,g(i) 是一个固定值(即 g(i) 不变化),此时我们直接计算整个区间内的误差:

    sum += (long long)abs(f_i - g_L) * (R - L);

    即,所有 i 值在这个区间内的误差都相同,直接乘上区间的长度 (R - L)

分段计算误差(当 g(i) 变化时)

如果 g(i) 在该区间内发生变化(即 g_L != g_R),我们需要将区间 [L, R) 分成多个子区间,每个子区间内 g(i) 是一个常数。这样可以分别计算每个子区间的误差

为什么使用 g_i * r(g_i + 1) * r 来计算左右端点

  1. g(x) 的变化

    • g(x) 是通过 ⌊ x / r ⌋ 来计算的,也就是说,如果 xg(x) = k 时,那么 k * r <= x < (k + 1) * r。例如,如果 r = 3,那么:
      • g(x) = 0 时,x 必须在 [0, 3) 区间。
      • g(x) = 1 时,x 必须在 [3, 6) 区间。
      • g(x) = 2 时,x 必须在 [6, 9) 区间。
      • 以此类推。
  2. 当前区间的左右端点

    • g(x) 值的变化会在 [g_i * r, (g_i + 1) * r) 这个区间之间发生。所以,对于每个 g(x) 值的区间,g(x) 是不变的,直到 x 达到下一个 g(x) 的分段边界。
    • g_i * r 就是当前 g(x) 值区间的左边界。
    • (g_i + 1) * r 就是下一个 g(x) 值区间的左边界。
  3. 计算有效区间: 对于每个 g(x),我们希望计算它在 [l, r) 区间中的误差。为了确保我们不超出这个区间,我们取 leftg_i * r 的较大值作为当前区间的左端点,取 right(g_i + 1) * r 的较小值作为当前区间的右端点:

    • seg_l = max(l, g_i * r):确保我们从真实的 left 开始,且不小于当前分段的左端点 g_i * r
    • seg_r = min(r, (g_i + 1) * r):确保我们在真实的 right 结束,且不超过当前分段的右端点 (g_i + 1) * r

这样,seg_Lseg_R 就代表了当前 g(x) 值在区间 [L, R) 中的有效部分。

举个例子

假设:

  • n = 3N = 10
  • r = 2
  • A = [0, 2, 5, 8]

我们想计算误差 error(A),需要对 f(i)g(i) 进行比较。考虑对于 i 的某个范围,我们如何根据 g(i) 来划分。

计算过程:

  1. 对于 i = 0 到 2g(i) = ⌊ i / 3 ⌋,所以 g(i) 都是 0。

    • 这里的有效区间就是 [0, 3),所以 g(i) 在这个区间内为 0。
  2. 对于 i = 3 到 5g(i) = ⌊ i / 3 ⌋ = 1

    • 这里的有效区间是 [3, 6),所以 g(i) 在这个区间内为 1。
  3. 对于 i = 6 到 8g(i) = ⌊ i / 3 ⌋ = 2

    • 这里的有效区间是 [6, 9),所以 g(i) 在这个区间内为 2。
  4. 对于 i = 9g(i) = ⌊ i / 3 ⌋ = 3

在代码中:

对于每个区间 [L, R),我们计算:

  • g_L = L / r
  • g_R = (R - 1) / r

然后将这个区间进一步拆分为 [g_i * r, (g_i + 1) * r),并根据 maxmin 保证没有超出原始的 [L, R) 区间。

总结

g_i * r(g_i + 1) * r 代表了 g(x) 值变化的边界。在每个区间内,我们通过这些边界来确保只在有效区间内计算误差。

 

 

第一轮:i = 0 (区间 [0, 2))

  • L = a[0] = 0, R = a[1] = 2
  • 计算:
    • f_i = 0,因为 A[0] ≤ x < A[1] 时,f(x) 为 0。
    • g_L = L / r = 0 / 2 = 0
    • g_R = (R - 1) / r = (2 - 1) / 2 = 0
  • 由于 g_L == g_R,在该区间内 g(i) 始终为 0。

    sum += (long long)abs(f_i - g_L) * (R - L) = abs(0 - 0) * (2 - 0) = 0

  • 误差为 0

第二轮:j = 1 (区间 [2, 5))

  • left = a[1] = 2, right = a[2] = 5
  • 计算:
    • f_i = 1,因为 A[1] ≤ x < A[2] 时,f(x) 为 1。
    • g_left = left / r = 2 / 2 = 1
    • g_right = (right - 1) / r = (5 - 1) / 2 = 2
  • 由于 g_left != g_rightg(i)[2, 5) 区间内发生变化。
    • 对于 g(i) 可能的值 12,计算每段的误差。
    • 对于 g_i = 1:

      segment_left = max(2, 1 * 2) = 2 segment_right = min(5, (1 + 1) * 2) = 4 sum += abs(f_i - g_i) * (segment_right - segment_left) = abs(1 - 1) * (4 - 2) = 0

    • 对于 g_i = 2:

      segment_left = max(2, 2 * 2) = 4 segment_right = min(5, (2 + 1) * 2) = 5 sum += abs(f_i - g_i) * (segment_right - segment_left) = abs(1 - 2) * (5 - 4) = 1

  • 误差为 1

 

第三轮:j = 2 (区间 [5, 8))

  • left = a[2] = 5, right = a[3] = 8
  • 计算:
    • f_i = 2,因为 A[2] ≤ x < A[3] 时,f(x) 为 2。
    • g_left = left / r = 5 / 2 = 2
    • g_right = (right - 1) / r = (8 - 1) / 2 = 3
  • 由于 g_left != g_rightg(i)[5, 8) 区间内发生变化。
    • 对于 g(i) 可能的值 23,计算每段的误差。
    • 对于 g_i = 2:
       

      cpp

      复制编辑

      segment_left = max(5, 2 * 2) = 5 segment_right = min(8, (2 + 1) * 2) = 6 sum += abs(f_i - g_i) * (segment_right - segment_left) = abs(2 - 2) * (6 - 5) = 0

    • 对于 g_i = 3:

      segment_left = max(5, 3 * 2) = 6 segment_right = min(8, (3 + 1) * 2) = 8 sum += abs(f_i - g_i) * (segment_right - segment_left) = abs(2 - 3) * (8 - 6) = 2

  • 误差为 2

 

 

第四轮:j = 3 (区间 [8, 10))

  • left = a[3] = 8, right = N = 10
  • 计算:
    • f_i = 3,因为 A[3] ≤ x < N 时,f(x) 为 3。
    • g_left = left / r = 8 / 2 = 4
    • g_right = (right - 1) / r = (10 - 1) / 2 = 4
  • 由于 g_left == g_right,在该区间内 g(i) 始终为 4。

    sum += (long long)abs(f_i - g_left) * (right - left) = abs(3 - 4) * (10 - 8) = 2

  • 误差为 2

 

  • 分段计算误差:代码的目标是计算每个整数 i 在区间 [a[j], a[j+1]) 内的误差。为了高效计算误差,利用了 g(i) 是按比例递增的这一特点。即 g(i) 会在区间内的某些值变动,在某些情况下,它是连续的。所以,代码通过分段的方式来处理这些变化。

  • 区间内的线性变化:对于区间 [a[j], a[j+1])f(i) 直接等于 j,而 g(i) 是根据 i 除以比例系数 r 计算出来的。g(i) 在该区间内是分段的,即在某些 i 值下,g(i) 是固定的,而在其他值下,它会变化。

 

相关文章:

CCF-CSP第24次认证第2题 --《序列查询新解》

4281. 序列查询新解 - AcWing题库 上一题“序列查询”中说道&#xff1a; A[A0,A1,A2,⋯,An]A[A0,A1,A2,⋯,An] 是一个由 n1n1 个 [0,N)[0,N) 范围内整数组成的序列&#xff0c;满足 0A0<A1<A2<⋯<An<N0A0<A1<A2<⋯<An<N。 基于序列 AA&#…...

Webpack 打包详细教程

Webpack 是一个现代 JavaScript 应用的静态模块打包工具&#xff0c;它可以处理 JavaScript、CSS、图片等资源&#xff0c;并优化它们以提高性能。以下是 Webpack 从基础到进阶的详细教程。 1. Webpack 基础概念 Webpack 的核心概念包括&#xff1a; Entry&#xff08;入口&a…...

每日一题----------集合

数组&#xff1a; &#xff08;1&#xff09;长度开始必须指定&#xff0c;而且一但指定&#xff0c;不能修改。 &#xff08;2&#xff09;保存的必须为同一类型的元素。 &#xff08;3&#xff09;使用数组进行增加元素的代码--比较麻烦。 如果要添加数据则需要&#xff…...

滑动窗⼝(同向双指针)---最⼤连续1的个数III

题目链接 给定一个二进制数组 nums 和一个整数 k&#xff0c;假设最多可以翻转 k 个 0 &#xff0c;则返回执行操作后 数组中连续 1 的最大个数 。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1,0,0,0,1,1,1,1,0], K 2 输出&#xff1a;6 解释&#xff1a;[1,1,1,0,0,…...

《几何原本》命题I.30

《几何原本》命题I.30 平行于同一直线的两条直线互相平行。 设 l 1 ∥ l 2 , l 1 ∥ l 3 l_1\parallel l_2,l_1\parallel l_3 l1​∥l2​,l1​∥l3​ 则 ∠ 1 ∠ 2 , ∠ 1 ∠ 3 \angle 1\angle 2,\angle 1\angle 3 ∠1∠2,∠1∠3 则 ∠ 2 ∠ 3 \angle 2\angle 3 ∠2∠3…...

蓝桥杯 k倍区间

题目描述 给定一个长度为 NN 的数列&#xff0c;A1,A2,⋯ANA1​,A2​,⋯AN​&#xff0c;如果其中一段连续的子序列 Ai,Ai1,⋯AjAi​,Ai​1,⋯Aj​ ( i≤ji≤j ) 之和是 KK 的倍数&#xff0c;我们就称这个区间 [i,j][i,j] 是 K 倍区间。 你能求出数列中总共有多少个 KK 倍区间…...

dify-SQL查询

第1节 DIFY 编排流程 1.1 步骤 1.开始&#xff1a;用户输入分析需求 2.LLM-SQL 专家&#xff1a;大模型根据用户输入需求生成 SQL 查询 3.SQL查询&#xff1a;执行查询并获取数据 4.结束&#xff1a;输出查询结果集 1.2 工作流 第2节 组件配置 2.1 开始 新建一个开始组件&am…...

【制作PPT的AI工具】

制作PPT的AI工具&#xff1a; 1. Gamma&#xff1a; 特点&#xff1a; 无需下载&#xff0c;支持网页、移动端及iPad使用。提供多种模板和主题&#xff0c;支持一键生成PPT大纲、排版和配图。优点&#xff1a; 操作简单&#xff0c;适合快速制作演示文稿。 2. Beautiful.ai&…...

贪心算法精解:用C++征服最优解问题

贪心算法精解&#xff1a;用C征服最优解问题 一、贪心算法的本质&#xff1a;当下最优即全局最优 贪心算法如同下棋高手&#xff0c;每一步都选择当前最优的走法。它的核心思想是&#xff1a;通过局部最优选择的叠加&#xff0c;最终得到全局最优解。这种算法在时间复杂度上往…...

《程序员的自我修养—链接、装载与库》-- 对书中常见段的讲解总结

1. 核心段的作用与特点 (1) .text 段&#xff08;代码段&#xff09; 内容&#xff1a;存放程序的可执行指令&#xff08;机器码&#xff09;&#xff0c;例如函数的实现代码。特点&#xff1a; 通常是只读的&#xff08;防止程序意外修改指令&#xff09;。在程序运行前已确…...

一文了解汽车图像传感器

2024年底,安森美做了题为"How Automotive Image Sensors Transform the Future of Autonomous Driving"的演讲,这里结合其内容对自动驾驶图像传感器做一个介绍。 当前的自动驾驶感知技术主要有两大技术路线:一种是仅使用摄像头作为传感器进行信息采集的纯…...

2025数据存储技术风向标:解析数据湖与数据仓库的实战效能差距

一、技术演进的十字路口 当前全球数据量正以每年65%的复合增长率激增&#xff0c;IDC预测到2027年企业将面临日均处理500TB数据的挑战。在这样的背景下&#xff0c;传统数据仓库与新兴数据湖的博弈进入白热化阶段。Gartner最新报告显示&#xff0c;采用混合架构的企业数据运营效…...

AI科技公司招聘一位后端开发工程师

招聘岗位&#xff1a;后端开发工程师&#xff08;兼运维&#xff09; 公司名称&#xff1a;深圳市格子科技有限公司 公司介绍&#xff1a;深圳市格子科技有限公司作为AI应用创新先锋&#xff0c;构建起以AI工具研发为核心、短剧平台运营为延伸的多业务发展模式。公司自主研发A…...

ubuntu软件

视频软件&#xff0c;大部分的编码都能适应 sudo apt install vlc图片软件 sudo apt install gwenview截图软件 sudo apt install flameshot设置快捷键 flameshot flameshot gui -p /home/cyun/Pictures/flameshot也就是把它保存到一个自定义的路径 菜单更换 sudo apt r…...

《面向对象程序设计-C++》实验一 熟悉Visual C++开发环境及上机过程

一、实验目的 了解和使用VC集成开发环境&#xff1b;熟悉VC环境的基本命令和功能键&#xff1b;熟悉常用的功能菜单命令&#xff1b;学习使用VC环境的帮助&#xff1b;学习完整的C程序开发过程&#xff1b;理解简单的C程序结构。 二、实验内容 使用Visual C 6.0集成环境来编…...

《2025年软件测试工程师面试》MySQL面试题

1、什么是数据库事务? 数据库事务: 是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 2、Mysql事务的四大特性是什么? 原子性: 事务作为一个整体被执行,包含在其中的对数据…...

Java的 JDBC 编程

1. Java的数据库编程&#xff1a;JDBC JDBC&#xff1a;Java 通过JDBC这样的技术来操作 MySQL MySQL 是一个基于 C/C 实现的数据库。 本身也提供了一系列的 API &#xff08;Application Progromming Interface&#xff09;&#xff0c;让程序员调用&#xff0c;从而通过代码来…...

Java中的分布式锁:原理、实现与最佳实践

引言 在分布式系统中&#xff0c;多个服务实例或进程需要协调对共享资源的访问。例如&#xff0c;电商系统中库存扣减、金融交易中的余额操作等场景&#xff0c;都需要保证同一时刻只有一个客户端能执行关键操作。**分布式锁&#xff08;Distributed Lock&#xff09;**正是解…...

开源!速度100Kb/s的有线和无线双模ESP32S3芯片的DAP-Link调试器

开源&#xff01;速度100Kb/s的有线和无线双模ESP32S3芯片的DAP-Link调试器 目录 开源&#xff01;速度100Kb/s的有线和无线双模ESP32S3芯片的DAP-Link调试器本项目未经授权&#xff0c;禁止商用&#xff01;本项目未经授权&#xff0c;禁止商用&#xff01;本项目未经授权&…...

深入剖析 Windows 崩溃:从 explorerframe.dll 到 Mwt.exe 的侦探之旅

抱歉复制后格式出现问题&#xff0c;可能是因为 Markdown 或纯文本在不同平台间的换行和缩进处理不一致。我重新整理了一份格式清晰的版本&#xff0c;确保在复制到博客平台&#xff08;如 WordPress、Medium&#xff09;或文本编辑器时更容易调整。以下是优化后的 Markdown 版…...

如何将ipynb文件转换为pdf文件

事情起因&#xff1a; 基本我所有的code以及代码注释&#xff0c;以及出图说明都统一放在jupyter notebook中&#xff0c; 代码注释&#xff0c;或者文档说明&#xff0c;实际上就是markdown所做的那一切&#xff0c;都是在markdown中写的&#xff1b; 代码的话&#xff0c;…...

具备多种功能的PDF文件处理工具

软件介绍 在日常办公和学习场景中&#xff0c;PDF文件使用极为频繁&#xff0c;而一款功能强大的PDF编辑软件能大幅提升处理效率。 今天要介绍的Adobe Acrobat Pro DC 2024.005.20414&#xff0c;就具备像编辑Word文档一样便捷编辑PDF的能力。 PDF文档在学习和工作中广泛应用…...

如何做好滚珠导轨的防尘工作?

滚珠导轨滑块在使用过程中&#xff0c;会吸附大量的灰尘和污垢&#xff0c;导致摩擦力增大&#xff0c;使用寿命缩短。那么&#xff0c;我们应该如何做好滚珠导轨的防尘工作呢&#xff1f; 1、使用防护罩&#xff1a;对于外露的滚珠导轨&#xff0c;可安装如螺旋弹簧钢带套管、…...

c语言库 strcpy函数介绍,以及实现

strcpy 函数介绍 strcpy 是 C 语言标准库中的一个字符串处理函数&#xff0c;定义在 <string.h> 头文件中。其作用是将一个字符串的内容从源地址复制到目标地址。 函数原型&#xff1a; char *strcpy(char *dest, const char *src);参数说明&#xff1a; dest&#xf…...

nettrace rtt分析器

开源工具学习记录之流程梳理 近期对腾讯的的开源项目: nettrace(网络故障分析工具) ,进行源码学习。 开源仓库&#xff1a;Nettrace开源仓库 开源工具实现注释&#xff1a;nettrace学习记录 Nettrace学习记录之流程梳理Nettrace eBPF程序自动挂载方式探究 nettrace rtt分析器…...

裂变营销策略在“开源链动2+1模式AI智能名片S2B2C商城小程序”中的应用探索

摘要&#xff1a;在当今数字化时代&#xff0c;企业营销手段日新月异&#xff0c;裂变营销作为一种高效的用户增长策略&#xff0c;正逐渐成为众多企业竞相探索的焦点。本文旨在探讨“开源链动21模式AI智能名片S2B2C商城小程序”中裂变营销的应用&#xff0c;通过“分名、分利、…...

VC++ 获取目的IP的路由

GetBestRoute 函数获取到目的IP的最佳匹配路由。 第一个参数为&#xff1a;destination&#xff08;目的IP&#xff09; 第二个参数为&#xff1a;source&#xff08;源IP&#xff09; 通常不需要指定第二个source&#xff0c;这个一般用来匹配具体某一个网卡接口路由的&…...

WangEditor快速实现版

WangEditor快速实现版 效果 案例代码 后端 package com.diy.springboot.controller;import cn.hutool.core.util.IdUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiImplicitParam; import org.sp…...

Java常见面试技术点整理讲解——后端框架(整理中,未完成)

前言&#xff1a; 对于后端常用框架的技术整理&#xff0c;其实框架在平时就是会用就行&#xff0c;但面试时多半需要描述实现原理&#xff0c;这个要靠自己理解&#xff0c;不推荐死记硬背。 这篇和另外几篇文章区分开&#xff0c;主要用于规整Java后端各种框架&#xff0c;…...

Dify 本地部署教程

目录 一、下载安装包 二、修改配置 三、启动容器 四、访问 Dify 五、总结 本篇文章主要记录 Dify 本地部署过程,有问题欢迎交流~ 一、下载安装包 从 Github 仓库下载最新稳定版软件包,点击下载~,当然也可以克隆仓库或者从仓库里直接下载zip源码包。 目前最新版本是V…...