目录

C蓝桥杯皮亚诺曲线距离求解

C++蓝桥杯皮亚诺曲线距离求解

一、题目概述

给定指定阶数的皮亚诺曲线,以及曲线上的两个点的坐标,求解两个点之间的距离, 曲线起点坐标规定为(0,0) 。

一阶皮亚诺曲线如下图:

https://i-blog.csdnimg.cn/direct/8a48d8cbd72644bcb679d13cf2fba39c.png#pic_center

k+1阶的皮亚诺曲线是在k阶曲线的基础上,每一个格子由一阶曲线代替而生成 。例如二阶曲线如下图:

https://i-blog.csdnimg.cn/direct/534997318cf74add90a1f9d84842ec4e.png#pic_center

三阶曲线如下图:

https://i-blog.csdnimg.cn/direct/6e9a47b9fb0c4b7ab353362d290217fc.png#pic_center

k阶曲线的边长为3的k次幂,格子总数为3的2k次幂。无论k取何值, 曲线的起点总为左下角,坐标为(0,0),终点为右上角,坐标为(3的2k次幂-1,3的2k次幂-1) 。

二、解题分析

2.1解题思路

经过初步分析:

  1. 如果直接求解两点间的距离,那么需要求解 任意两点间的详细路径,难度很大 ;
  2. 可以将求两点间距离 转换为求出到原点的距离 然后相减;
  3. k+1阶曲线是在 1阶曲线的基础上层层细化网格 而成,那么任意一点到原点的 距离也可以先从宏观处入手,层层细化求解 。

一阶曲线 按照从起点到终点的 行走顺序对网格进行划分 ,那么可 分为1~9个区域 ,如下图所示:

https://i-blog.csdnimg.cn/direct/8c94582f56a144e3a2ea9792f378a025.png#pic_center

任意k阶曲线都可以划分为上图所示的9个区域 ,例如二阶曲线划分如下图:

https://i-blog.csdnimg.cn/direct/a269838d9ef14a308712a28dfdf3e760.png#pic_center

假设k阶曲线上的任意一点, 该点到原点的距离=该点到所在区域起点的距离+本区域之前的区域网格数累加和 ,而 该点到所在区域起点的距离 又可以 转换 为 以所在区域起点为原点的求解到原点距离 的问题,因此 可以层层递归求解 。

根据上述解题思路,细化解题步骤如下:

  1. 首先 判断出k阶曲线上任意一点(x,y)所在区域 ,每个区域的边长为3的k-1次幂,因此可以通过对比大小得出判断;
  2. 点到所在区域起点的距离 如果要转换 为以所在区域起点为原点的点到该原点的距离, 就要将区域的起点映射为区域1的起点,即原点 , 该映射不仅仅是平移,可能还要旋转 ;
  3. 以二阶曲线为例,区域2的起点转换为区域1的起点。区域2的起点坐标为(2,3),如果要转换到(0,0),那么就需要进行映射(2-x,y-3),(x,y)为区域2内的点, 2-x说明区域2进行了X轴方向的翻转 ;
  4. 本区域之前的区域网格数累加计算较为简单,每个区域的网格数为3的2k-2次幂,例如区域5之前的区域网格数累加,就等于4×3的2k-2次幂。

解题步骤大致如上,读者对 皮亚诺曲线随阶数的增加而在形状上层层裂变 的过程有一个 形象的想象过程 ,有助于理解解题步骤。

2.2k值范围限制

由于题目中说明k值范围为1~100,而点的坐标的范围为不超过10的18次方,显然3的100次幂超过了10的18次方,同时也超过了C++中long long类型可表示的范围,因此 不能直接将k值代入进行求解 ,而是要 先判断出在10的18次方范围内的最大k值 ,判断代码如下:

void test_k()
{
	int k=0;
	long long p1;
	while(true)
	{
		p1=pow(3, k);
		if(p1>=1e18)break;
		k++;
	}
	cout<<k<<endl;
	cout<<p1<<endl;
}

得出 k值最大为38 。

三、实现代码

代码使用C++实现,如下:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

int k;
long long x11,y11,x12,y12;

long long func01(long long x, long long y, int k)
{
    if(k == 0) return 0;
    long long tmp=pow(3, k-1);
    if(x < tmp && y < tmp) return func01(x, y, k-1);//region1 
    if(x < tmp && (y >= tmp && y < tmp*2)) return tmp*tmp + func01(tmp-1-x, y-tmp, k-1);//region2
    if(x < tmp && (y >= tmp*2 && y < tmp*3)) return 2*tmp*tmp + func01(x, y-tmp*2, k-1);//region3
    if((x >= tmp && x < tmp*2) && y < tmp) return 5*tmp*tmp + func01(x-tmp, tmp-1-y, k-1);//region6
    if((x >= tmp && x < tmp*2) && (y >= tmp && y < tmp*2)) return 4*tmp*tmp + func01(tmp*2-1-x, tmp*2-1-y, k-1);//region5
    if((x >= tmp && x < tmp*2) && (y >= tmp*2 && y < tmp*3)) return 3*tmp*tmp + func01(x-tmp, tmp*3-1-y, k-1);//region4
    if((x >= tmp*2 && x < tmp*3) && y < tmp) return 6*tmp*tmp + func01(x-tmp*2, y, k-1);//region7
    if((x >= tmp*2 && x < tmp*3) && (y >= tmp && y < tmp*2)) return 7*tmp*tmp + func01(tmp*3-1-x, y-tmp, k-1);//region8
    if((x >= tmp*2 && x < tmp*3) && (y >= tmp*2 && y < tmp*3)) return 8*tmp*tmp + func01(x-tmp*2, y-tmp*2, k-1);//region9
    return -1;
}

int main()
{
    cin>>k;
    cin>>x11>>y11;
    cin>>x12>>y12;
    if(k > 38) k=38;
    cout<<abs(func01(x11,y11,k)-func01(x12,y12,k))<<endl;
    return 0;
}

此题代码并不长,关键在于解题思路。

四、代码测试

4.1蓝桥杯测试平台

为测试代码正确性,可以在蓝桥杯的刷题平台提交代码,网址为 。

皮亚诺曲线距离题目编号为141,如下图所示:

https://i-blog.csdnimg.cn/direct/e730ce62503c4c6c92a1ee83272f4a74.png#pic_center

4.2直接传入原始输入的k值

在蓝桥杯解题平台对代码进行测试,当将 k值直接传入递归函数 时, 可以得到90%的分数 ,显示10个测试实例中有一个答案错误,如下图所示:

https://i-blog.csdnimg.cn/direct/90ee0cfcb0bc4de390a6cf2c85fd412f.png#pic_center

在本地运行代码,发现确实是 因为3的100次方超出了long long类型的范围 ,导致错误输出。

4.3限制k值大小

对k值大小进行限制 ,采用了本文第三节中给出的代码,在蓝桥杯平台提交,结果 依然显示10个测试实例中有一个答案错误 ,后将该错误实例的输入数据与输出数据下载到本地,测试数据如下:

kx11x12y11y12
100972800214282722763781912860110024270972800214336621164781912860202693276

正确输出应为191503939959914635。

在本地运行程序, 得到的输出为191503939959943987,确实与答案不一致 ,经过一番检查后发现,是 由于pow函数导致的答案错误 。

4.4pow函数求整数高次幂存在误差

分别使用 pow函数 求3的k次幂与 连续乘积计算 3的k次幂 测试结果的一致性 ,代码如下:

void test_pow()
{
	int k=1;
	long long p1,p2=1;
	while(true)
	{
		p1=pow(3, k);
		p2*=3;
		if(p1>=1e18)break;
		k++;
		cout<<p1<<','<<p2<<endl;
	}
	cout<<k<<endl;
	cout<<p1<<endl;
}

输出如下图:

https://i-blog.csdnimg.cn/direct/626fbe0a20a34b4e924ecc5d228e8d66.png#pic_center

从输出结果分析, 当k≥35时,两种方法计算出的3的k次幂出现了不同 ,且差异会越来越大。通过查找资料得知: pow函数返回的是double类型,在被强制转换为整型时会出现误差 。

4.5满分代码

因此代码中 不再使用pow函数 ,而是通过连续乘积计算幂,最终代码如下:

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;

int k;
long long p[100];
long long x11,y11,x12,y12;

long long func01(long long x, long long y, int k)
{
    if(k == 0) return 0;
    //long long tmp=pow(3, k-1);
    long long tmp=p[k-1];
    if(x < tmp && y < tmp) return func01(x, y, k-1);//region1 
    if(x < tmp && (y >= tmp && y < tmp*2)) return tmp*tmp + func01(tmp-1-x, y-tmp, k-1);//region2
    if(x < tmp && (y >= tmp*2 && y < tmp*3)) return 2*tmp*tmp + func01(x, y-tmp*2, k-1);//region3
    if((x >= tmp && x < tmp*2) && y < tmp) return 5*tmp*tmp + func01(x-tmp, tmp-1-y, k-1);//region6
    if((x >= tmp && x < tmp*2) && (y >= tmp && y < tmp*2)) return 4*tmp*tmp + func01(tmp*2-1-x, tmp*2-1-y, k-1);//region5
    if((x >= tmp && x < tmp*2) && (y >= tmp*2 && y < tmp*3)) return 3*tmp*tmp + func01(x-tmp, tmp*3-1-y, k-1);//region4
    if((x >= tmp*2 && x < tmp*3) && y < tmp) return 6*tmp*tmp + func01(x-tmp*2, y, k-1);//region7
    if((x >= tmp*2 && x < tmp*3) && (y >= tmp && y < tmp*2)) return 7*tmp*tmp + func01(tmp*3-1-x, y-tmp, k-1);//region8
    if((x >= tmp*2 && x < tmp*3) && (y >= tmp*2 && y < tmp*3)) return 8*tmp*tmp + func01(x-tmp*2, y-tmp*2, k-1);//region9
    return -1;
}

int main()
{
    cin>>k;
    cin>>x11>>y11;
    cin>>x12>>y12;
    if(k > 38) k=38;
    p[0]=1;
    for(int i = 1; i <= k; i++) p[i]=p[i-1]*3;
    cout<<abs(func01(x11,y11,k)-func01(x12,y12,k))<<endl;
    return 0;
}

上述代码提交后,全部10个用例全部跑通。

https://i-blog.csdnimg.cn/direct/3a9217f48db94da2b2a331a3d5d29900.png#pic_center

附录

error: ‘long long int y1’ redeclared as different kind of symbol报错

初始编写上述代码时,第一个点的坐标变量本来定义为x1,y1,代码在本地Dev-C++编译器上能够正确运行,但是 在蓝桥杯解题平台上运行就报出该错误 ,后经查找资料后发现, cmath头文件中定义了与y1同名的函数 ,所以导致报错,因此上述代码中的变量名改为x11,y11,x12,y12。

C++中int类型与long long类型取值范围

类型专用存储空间表示范围次方表示
int4个字节-2,147,483,648~2,147,483,647− 2 31 -2 ^{31} − 2 31 ~ 2 31 2 ^{31} 2 31 -1,约为10的9次方
unsigned int4个字节0~4,294,967,2950 ~ 2 32 2 ^{32} 2 32 -1,约为10的9次方
long4个字节-2,147,483,648~2,147,483,647− 2 31 -2 ^{31} − 2 31 ~ 2 31 2 ^{31} 2 31 -1,约为10的9次方
unsigned long4个字节0~4,294,967,2950 ~ 2 32 2 ^{32} 2 32 -1,约为10的9次方
long long8个字节-9,223,372,036,854,775,808~9,223,372,036,854,775,807− 2 63 -2 ^{63} − 2 63 ~ 2 63 2 ^{63} 2 63 -1,约为10的18次方
unsigned long long8个字节0~18,446,744,073,709,551,6150 ~ 2 64 2 ^{64} 2 64 -1,约为10的19次方