week8-csp-C(咕咕东的奇妙序列)

题意

咕咕东 正在上可怕的复变函数,但对于稳拿A Plus的 咕咕东 来说,她早已不再听课,此时她在睡梦中
突然想到了一个奇怪的无限序列:112123123412345 …这个序列由连续正整数组成的若干部分构成,其
中第一部分包含1至1之间的所有数字,第二部分包含1至2之间的所有数字,第三部分包含1至3之间的所
有数字,第i部分总是包含1至i之间的所有数字。所以,这个序列的前56项会是
11212312341234512345612345671234567812345678912345678910,其中第1项是1,第3项是2,第20项是
5,第38项是2,第56项是0。咕咕东 现在想知道第 k 项数字是多少!但是她睡醒之后发现老师讲的东西
已经听不懂了,因此她把这个任务交给了你。

输入格式:
输入由多行组成。
第一行一个整数q表示有q组询问
接下来第i+1行表示第i个输入Ki ,表示询问第 Ki项数字。
输出格式:
输出包含q行
第i行输出对询问 的输出结果。

样例:
input:
5
1
3
20
38
56
ouput:
1
2
5
2
0

数据范围:
在这里插入图片描述

思路(借鉴某大佬)

  • 这个题首先是数据范围很大,很有规律,题目要求是得到第Ki项,那么我们首先应该得知总共有多少项;我们通过观察发现这些数据,可以将这些数据按位进行区分;成很多块;

例如 1-9 10-99 100-999,这每一位区分一块

  • 我们通过计算每一块的字符数目,然后累计就能算出第i块(包括第i块)之前有多少字符;如何进行每一块字符的数目的计算:
  • 对于每一块我们发现这是一个梯形,其相关数据可以这样来计算

	while (i <= 18)
	{
		number_block = i;
		group[i] = i * height + group[i - 1];//第i组的数据个数
		upper[i] = below[i - 1] + i;  //梯形上底
		below[i] = upper[i] + i * (height - 1);  //梯形下底
		sum[i] = sum[i - 1] + ((upper[i] + below[i]) * height )/ 2;//累计梯形面积,即累计字符数目
		if (sum[i] > 1e18)
			break;//达到最大数据范围
		height = height * 10;//每一块之间是按位数分的,所以比例为10
		i++;
	}
  • 我们可以通过比较k和sum[i]就可以确定这一项在哪一个块中,k进行更新后,表示第i块中第k个位置;
  • 但是可以发现每一个块的字符数目特别多,根据在梯形特点,有很多行,那么我们需要做的就是得到k属于sum[i]的哪一行,此处我们采用二分法进行获取,k再次进行更新后,k表示某行的第k个位置;
  • 当获取了行数,一行的数据也是非常惊人的,按其数据构造特点,不能直接进行找,所以我们需要分组,去看这个数属于哪一组,我们使用之前的group数组,将k与group[j]进行比较,得到k属于的组,更新k,更新后的k表示在第j组的第k个位置;
  • 我们得到k的组数以后,我们先应该获得k属于第几个数,然后我们通过数据特点可以得到k所属的数的值,得到以后,我们将其to_string为一个字符串,然后我们需要得到的是k在这个数的哪个位置,通过k%j,得到其组数;然后输出即可;

总结

这个题使用多次搜索,依次缩小搜索范围,然后获得最后的值,使用梯形的特点简化字符数目的求取,借鉴大佬的博客,自己收获很大,题目很难!

代码

#include<iostream>
#include<string>
#include<cmath>
using namespace std;
long long qq[1000], upper[20], below[20], sum[20],group[20], height = 0;
void init()
{
	memset(upper, 0, sizeof upper);
	memset(below, 0, sizeof below);
	memset(sum, 0, sizeof sum);
	memset(group, 0, sizeof group);
}
int main()
{
	int q;
	cin >> q;
	init();//数组初始化
	for (int i = 0; i < q; i++)
		scanf("%lld", &qq[i]);
	//首先进行字符数目的计算,根据数据范围,最多可将所有的数据按位分为18块,然后进行计算每一块的字符数目,累计存储在sum数组
	int i = 1;
	int number_block = 0;//块数
	height = 9;//初始一位的有九个数据
	//每一块的字符可以看做一个梯形的面积去计算,分为上底upper,下底below,和高height,
	//每一行的数据到后期会很多,所以我们将其分组,累计存储在group数组

	while (i <= 18)
	{
		number_block = i;
		group[i] = i * height + group[i - 1];//第i组的数据个数
		upper[i] = below[i - 1] + i;
		below[i] = upper[i] + i * (height - 1);
		sum[i] = sum[i - 1] + ((upper[i] + below[i]) * height )/ 2;//累计梯形面积,即累计字符数目
		if (sum[i] > 1e18)
			break;//达到最大数据范围
		height = height * 10;//每一块之间是按位数分的,所以比例为10
		i++;
	}


	//找数据
	for (int j = 0; j < q; j++)
	{
		for (int i = 1; i <= number_block; i++)//首先寻找在那一块
		{
			if (qq[j] <= sum[i]) {
				qq[j] =	qq[j]- sum[i - 1];//定位到所在的块,修改需要查询的所在位置,对应在所在块

				//定位到第i块,但是由于块的层数很多,需要进行找到是哪一个层;
				//使用二分法进行求取到层
				height = ((below[i]-upper[i])/i)+1;
				
				long long left = 1, right = height,level = 0;//level记录是那一层
				while (left + 1 < right)
				{
					long long mid = (left + right) / 2;
					long long temp_below = upper[i] + i * (mid - 1);
					long long temp = (upper[i] + temp_below) * mid / 2;
					if (temp > qq[j])//在左边
						right = mid;
					else
						left = mid;
				}
				//由于最终也不知道mid等于left,还是right
				//进行判断
				long long temp_below = upper[i] + i * (left - 1);
				if ((upper[i] + temp_below) * left / 2 < qq[j])
					level = right;
				else
					level=left;
				//当找到qq[j]是哪一层,但是这一层可能很长,我们需要根据之前的group分组,找到第几个组;
				qq[j] = qq[j] - ((upper[i] + (upper[i] + i * (level - 2))) * (level - 1)) / 2;//计算qq[j]在这一层的位置
				//进行分组寻找
				for (int k = 1; k <= i; k++)
				{
					if (qq[j] <= group[k]) {
						qq[j] = qq[j] - group[k - 1];//定位到这一组以及组中的位置
						//知道组数以后,我们就在组中寻找第qq[j]个元素
						
						int weishu_number;//qq[i]在数的第几位
						if (qq[j] % k == 0)
							weishu_number = k;
						else
							weishu_number = qq[j] % k;
						qq[j] = ceil(qq[j] * 1.0 / k * 1.0);//第k组的第qq[i]个数;
						int number = qq[j] + pow(10, k - 1) - 1;//找到这个数
						//将这个数转化为字符串,得到其长度
						string num = to_string(number);//将其转化为一个字符串
						
						printf("%c\n", num[weishu_number-1]); 
						num.clear();
						break;
					}
					
				}
					break;
			}
		}
	}
		return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值