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(logn) 的时间复杂度内轻松解决。但在 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 0≤i<n时,处理[A[i],A[i+1]);
当i=n时,处理[A[n],N);
2、当 0 ≤ i < n 0\leq i<n 0≤i<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);
}