题意
咕咕东 正在上可怕的复变函数,但对于稳拿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;
}