2021-12-2序列查询新解(区间划分,不用前缀和的最简解法)(c/c++实测满分)

总结:

        出现超时问题时有双重循环,将双重循环改至单层循环或找到两层循环的关系改用双指针;只有单层循环,看循环判断条件能否减小以减少循环次数。有相邻未知数相等的情况考虑能否进行区间划分,将相等的未知数划分为同一个区间,以区间为单位进行运算以减少运算次数。

一、题目要求

题目背景

上一题“序列查询”中说道:
A=[A0,A1,A2,⋯,An] 是一个由 n+1 个 [0,N) 范围内整数组成的序列,满足 0=A0小于等于 x 的整数里最大的数的下标

对于给定的序列 A 和整数 x,查询 f(x) 是一个很经典的问题,可以使用二分搜索在 O(log⁡n) 的时间复杂度内轻松解决。但在 IT 部门讨论如何实现这一功能时,小 P 同学提出了些新的想法。

题目描述

小 P 同学认为,如果事先知道了序列 A 中整数的分布情况,就能直接估计出其中小于等于 x 的最大整数的大致位置。接着从这一估计位置开始线性查找,锁定 f(x)。如果估计得足够准确,线性查找的时间开销可能比二分查找算法更小。

比如说,如果 A1,A2,⋯,An 均匀分布在 (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)| 来表示处理询问 x 时的误差。

为了整体评估小 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) 的值。

样例1输入

3 10
2 5 8

Data

样例1输出

5

二、我的解法(70)

#include
#include
using namespace std;

const int M=1e5;
int A[M];

int search(int a,int b,int x){//二分查找求f
	int l=a,r=b;
	while(l>n>>N;
	A[0]=0;
	
	int r=N/(n+1);
	for(int i=1;i<=n;i++){
		scanf("%d",&A[i]);		
	}
	
	int res=0,f,m=0;
	for(int i=0;i=A[m]&&m#include
#include
using namespace std;

const int M=1e5;
int A[M];

int main(){
	int n,N;
	cin>>n>>N;
	A[0]=0;
	
	int r=N/(n+1);
	for(int i=1;i<=n;i++){
		scanf("%d",&A[i]);		
	}
	A[++n]=N;//处理最后一段A[n]到N-1

	long long res=0;//注意res必须为long long否则溢出出错
	int f=0;
	int left,mid,right;

	for(int i=1;i<=n;i++){//分大段,每次走到f+1的位置
		left=A[i-1];
		right=A[i]-1;//注意要-1

		if((left/r)!=(right/r)){//该大段内有多个g值
			for(mid=left+(r-left%r-1);mid<=right;mid+=r){//分小段,每次走到g+1的位置
				res+=abs((left/r-f)*(mid-left+1));//mid前为旧g,mid后为新g
				left=mid+1;
			}
			res+=abs((right/r-f)*(right-left+1));//处理最后一小段
		}
		else{//该大段g相同,不用分小段
			res+=abs((left/r-f)*(right-left+1));
		}

		f++;
	}
	
	cout<

 分析:

        抓住从A[i]到A[i]-1段的 f 不会发生改变的特点可以将区间按 f 的值分成大段,从k*r 到(k+1)*r-1段的 g 不会发生改变的特点可以将大段区间按照 g 的值分成小段。每一小段的 f 和 g 都相同,则求差时可以由一点的差乘以区间长度算区间差之和,减少运算次数。根据区间分段的思想,可以将暴力解法中的for循环条件由 N 改为 n,减少循环次数,解决超时问题。

你可能感兴趣的:(ccf真题,c++,算法)