202112-2 序列查询新解
题目背景
上一题“序列查询”中说道:
A=[A0,A1,A2,⋯,An] 是一个由 n+1 个 [0,N) 范围内整数组成的序列,满足 0=A0<A1<A2<⋯<An<N。基于序列 A,对于 [0,N) 范围内任意的整数 x,查询 f(x) 定义为:序列 A 中小于等于 x 的整数里最大的数的下标。
对于给定的序列 A 和整数 x,查询 f(x) 是一个很经典的问题,可以使用二分搜索在 O(logn) 的时间复杂度内轻松解决。但在 IT 部门讨论如何实现这一功能时,小 P 同学提出了些新的想法。
题目描述
小 P 同学认为,如果事先知道了序列 A 中整数的分布情况,就能直接估计出其中小于等于 x 的最大整数的大致位置。接着从这一估计位置开始线性查找,锁定 f(x)。如果估计得足够准确,线性查找的时间开销可能比二分查找算法更小。
比如说,如果 A1,A2,⋯,An 均匀分布在 (0,N) 的区间,那么就可以估算出:f(x)≈(n+1)⋅x / N
为了方便计算,小 P 首先定义了比例系数 r=⌊N/(n+1)⌋,其中 ⌊ ⌋ 表示下取整,即 r 等于 N 除以 n+1 的商。进一步地,小 P 用 g(x)=⌊x/r⌋ 表示自己估算出的 f(x) 的大小,这里同样使用了下取整来保证 g(x) 是一个整数。
显然,对于任意的询问 x∈[0,N),g(x) 和 f(x) 越接近则说明小 P 的估计越准确,后续进行线性查找的时间开销也越小。因此,小 P 用两者差的绝对值 |g(x)−f(x)| 来表示处理询问 x 时的误差。
为了整体评估小 P 同学提出的方法在序列 A 上的表现,试计算:
error(A)=∑i=0~N−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) 的值。
样例1输入
样例1输出
样例1解释
样例1解释
A=[0,2,5,8]
r=⌊N/n+1⌋=⌊10/3+1⌋=2
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| f(i) | 0 | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 3 | 3 |
| g(i) | 0 | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 |
| |g(i)−f(i)| | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 1 | 1 | 1 |
样例2输入
样例2输出
样例3输入
样例3输出
样例3解释
A=[0,1,3]
r=⌊Nn+1⌋=⌊102+1⌋=3
| i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| f(i) | 0 | 1 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
| g(i) | 0 | 0 | 0 | 1 | 1 | 1 | 2 | 2 | 2 | 3 |
| |g(i)−f(i)| | 0 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 |
子任务
70% 的测试数据满足 1≤n≤200 且 n<N≤1000;
全部的测试数据满足 1≤n≤105 且 n<N≤109。
提示
需要注意,输入数据 [A1⋯An] 并不一定均匀分布在 (0,N) 区间,因此总误差 error(A) 可能很大。

运行超时——把分区和的计算用乘法代替
n,N=map(int,input().split())#3 10 会分成4区 a=[0]+list(map(int,input().split()))+[N]#0 N 界限 #A=[0,2,5,8]+[10] r=int(N/(n+1)) g=[] for i in range(N): g.append(int(i/r)) e=0 #注意fx不能用给的int((n+1)*i/N)来算 ''' 4个区域0 1 2 3 此区间内f取值一样 都是i-1 1-1都是0 2-4都是1 5-7都是2 8-9都是3''' for i in range(1,n+2):#n+1个小区域 所以 #以f(i)为区域 划分 i是a的下标 for j in range(a[i-1],a[i]):#小区间的跨度 fg=abs(i-1-g[j])#f=i-1 e+=fg print(e)
c++
#include<cstdio> #include<cmath> int main() { long n; long N; scanf("%ld %ld", &n, &N); long long r = N/(n+1); long long a[100010]; a[0] = 0; for(int i=1; i<=n; i++) { scanf("%lld", &a[i]); } a[n+1] = N; long long fx, gx; //fx,gx为当前计算的函数值 long long dr = r; //dr为内层循环每次要移动的位次 long long ddr = 0; //用于处理边界情况时的临时变量 int flag = 0; //用于标记dr是否改变 long long error = 0; //以a[i]的值作为外层循环的界限 for(long long i=0; i<=n; i++) { fx = i; //在每段a[i]与 a[i+1]的间隔中,gx的值以r为间隔而变化 for(long long j=a[i]; j<a[i+1]; j=j+dr){ gx = j/r; //上一个边界情况之后,上一段g(x)相等段中还有遗留未计算的部分,对其进行处理 if(flag == 1) { dr = ddr; flag = 0; //标记边界情况已处理完毕 }else { dr = r; //边界情况处理完之后,重新将循环间隔变回r } //当前段处于a[i]和a[i+1]中间,直接计算error值 if(j+dr-1 < a[i+1]) { error += abs(fx-gx)*dr; }else { //j靠近a[i+1]的边界情况,即 j+dr-1 >= a[i+1]时 error += abs(fx-gx)*(a[i+1]-j); ddr = dr-(a[i+1]-j); //记录当前g(x)值相同的长度为r的段内,尚未计算error值部分的长度 flag = 1; //标记当前段内有尚未计算error值的部分 } } } printf("%lld", error); return 0; }

浙公网安备 33010602011771号