CCF CSP 序列查询新解

CCF CSP 序列查询新解(C语言)

题目背景

上一题“序列查询”中说道:
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)⋅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

样例1输出

5

代码思路

初始时,我采用暴力求解,即把f(x)和g(x)分别求出,然后再求error,不出意外最后只有70分。

# include<stdio.h>
# include<malloc.h>
# include<math.h>
int main(void)
{
	int n, N, tmpi=0, tmpj=1;
	int error = 0;
	scanf("%d %d", &n, &N);
	int* A = (int*)malloc(sizeof(int) * (n+1));
	int* sum = (int*)malloc(sizeof(int) * N);
	A[0] = 0;
	int r = N / (n + 1);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &A[i]);
	}
	for (int i = 0; i < N; i++)
	{
		if (i > A[0])
		{
			for (int j = tmpj; j <= n; j++)
			{
				if (A[j] > i && i >= A[j - 1])
				{
					for (int k = i; k < A[j]; k++)
					{
						error += abs(j - 1 - int(k / r));
					}
					i = A[j] - 1;
					tmpj = j;
					break;
				}
				else if (i >= A[n])
				{
					for (int k = i; k < N; k++)
					{
						error += abs(n - int(k / r));
					}
					i = N;
				}
			}
		}
	}
	printf("%d", error);
}

随后我想到可以遍历序列A,而不是遍历(0, N),这样就会大大减小时间复杂度,具体思路如下(100分):

1、当 0 ≤ i < n 0\leq i<n 0i<n时,处理[A[i],A[i+1]);

​ 当i=n时,处理[A[n],N);

2、当 0 ≤ i < n 0\leq i<n 0i<n时,用tmp1存储g(A[i+1]-1),得到该区间最大的g值;用tmp2存储存储g(A[i]),得到该区间最小的g值;

​ 当i=n时,用tmp1存储g(N-1),得到该区间最大的g值;用tmp2存储存储g(A[n]),得到该区间最小的g值;

3、如果tmp1>i或者tmp2<i,代表出现误差;

4、如果tmp1=tmp2,则整个区间的误差值相同,则可用区间大小*误差值tmpj计算该区间的误差;

5、如果 t m p 1 ≠ t m p 2 tmp1\neq tmp2 tmp1=tmp2,则整个区间的误差值不同,用tmpi存储误差值发生变化的关键位置,再用同一个误差值区间大小*误差值diff求和得到该区间的误差;

6、依次遍历整个序列A。

# include<stdio.h>
# include<malloc.h>
# include<math.h>

int main(void)
{
	int n, N, tmpj=1, tmp1 =0, tmp2=0, flag=0, diff=0, len=0;
	long long error = 0;  //error最后会很大,故使用long long型
	int* tmpi;
	scanf("%d %d", &n, &N);
	int* A = (int*)malloc(sizeof(int) * (n+1));
	A[0] = 0;
	int r = N / (n + 1);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", &A[i]);
	}
	for (int i = 0; i <= n; i++)
	{
		flag = 0;
		if (i < n)
		{
			tmp1 = int((A[i + 1] - 1) / r);
			tmp2 = int(A[i] / r);
		}
		else if (i == n)
		{
			tmp1 = int((N - 1) / r);
			tmp2 = int(A[i] / r);
		}
		
		if (tmp1 > i || tmp2 < i)
		{
			
			if (tmp1 == tmp2)
		    {
				tmpj = abs(tmp1-i);
				if(i < n)
					error += (A[i + 1] - A[i]) * tmpj;
				else if(i == n)
					error += (N - A[i]) * tmpj;
		    }
			else
			{
				len = abs(tmp1 - tmp2);
				tmpj = tmp1-i;
				tmpi = (int*)malloc(sizeof(int) * (len + 2));
				tmpi[len + 1] = (i==n)?N:A[i+1];
				for (int j = (tmp2-i); j <= tmpj; j++)
				{
					tmpi[flag] = (i + j) * r;
					if (tmpi[flag] < A[i])
						tmpi[flag] = A[i];
					if(flag > 0)
						error += (tmpi[flag] - tmpi[flag-1]) * diff;
					flag++;
					diff = abs(j);
				}
				error += (tmpi[flag] - tmpi[flag - 1]) * diff;
			}
		}
	}
	printf("%lld", error);
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值