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(log⁡n) 的时间复杂度内轻松解决。但在 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输入

3 10
2 5 8
Data

样例1输出

5
Data

样例1解释

样例1解释

A=[0,2,5,8]

r=⌊N/n+1⌋=⌊10/3+1⌋=2

i0123456789
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输入

9 10
1 2 3 4 5 6 7 8 9
Data

样例2输出

0
Data

样例3输入

2 10
1 3
Data

样例3输出

6
Data

样例3解释

A=[0,1,3]

r=⌊Nn+1⌋=⌊102+1⌋=3

i0123456789
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;
}

 

posted @ 2023-03-07 00:26  吃人不吐葡萄  阅读(80)  评论(0)    收藏  举报