咕咕东的奇妙序列(多重搜索)

时间限制1s,空间限制64MB

问题描述

咕咕东 正在上可怕的复变函数,但对于稳拿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 项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。

Input

输入由多行组成。
第一行一个整数q表示有q组询问(1<=q<=500)
接下来第i+1行表示第i个输入 k i k_i ki ,表示询问第 k i k_i ki 项数字。 ( 1 < = k i < = 1 0 18 ) (1<=k_i<=10^{18}) (1<=ki<=1018)

Output

输出包含q行
第i行输出对询问 k i k_i ki 的输出结果。

Sample input

5
1
3
20
38
56

Sample output

1
2
5
2
0

数据范围

在这里插入图片描述

解题思路

这题开始没想出正确思路,看了看空间限制,直接打的表,就是将下面代码输出的结果存到string s中,然后输入一个k,直接输出s[k-1],成功混到了60分。(代码在贴在网站上一共8000行)

for (int i=1; i<=855; i++)
	for (int j=1; j<=i; j++)
	        cout<<j;

这个题正确的思路应该是划分字符串后搜索。

字符串第一部分是1,第二部分是 1 ∼ 2 1\sim 2 12,第三部分是 1 ∼ 3 , ⋯ ⋯ 1\sim 3,\cdots\cdots 13,,因此,第 i i i部分就是 1 ∼ i 1\sim i 1i。我们想象字符串按照下图排列:
在这里插入图片描述

分块

i i i行就是第 i i i部分,也就是第 i i i层,我们命名 i i i为当前层的代表数,现在我们分块,将字符串按照 i i i的位数划分,划分成 1 ∼ 9 , 10 ∼ 99 , 100 ∼ 999 , ⋯ 1\sim 9,10\sim 99,100\sim 999,\cdots 19,1099,100999,。如下图所示:
在这里插入图片描述
其中,第1块就是第1-9层,第2块就是10-99层,依次类推。

然后我们要用到sum数组,sum[i]记录第 1 ∼ i 1\sim i 1i块字符总数量,用梯形面积计算公式可以计算第 i i i块面积(也就是字符个数),使用up[i]存储第 i i i块上底的长度(也就是上底的字符个数),使用down[i]存储下底的长度,len存储sum数组长度,num数组我们后面说。当sum[i]>=1e18的时候循环就可以退出了,代码如下:

height=9;
for(int i=1; ;i++){//计算到此块为止字符数
    len=i; num[i]=i*height+num[i-1];
    up[i]=down[i-1]+i;//上底
    down[i]=up[i]+i*(height-1);//下底
    sum[i]=sum[i-1]+((up[i]+down[i])*height)/2;//梯形面积加三角形面积
    if(sum[i]>=1e18) break;
    height*=10;//高
}

然后我们可以根据sum数组和k的比较,来确定当前k属于哪一块:

for (int i=1; i<=len; i++){
	if(k<=sum[i]){
		//在第i块
	}
}
分层

确定块后,将k减去sum[i-1],此时问题就变成了从当前块中,搜寻第k个元素。这时需要搜k属于第几层,由于第 i i i块我们将其看作梯形,那么从上底到下底元素个数其实是一个等差数列,公差为 i i i,我们可以使用二分搜索来搜是第几层。代码如下:

height=((down[i]-up[i])/i)+1;//当前梯形的高,也就是等差数列的个数
long long left=1,right=height,level=0;
while(left+1<right){//二分层数,搜层
    long long mid=(left+right)/2;
    long long temp=((up[i]+(up[i]+i*(mid-1)))*mid)/2;//temp是上半个梯形的面积
    if(temp>k) right=mid;//在上面
    else left=mid;//在下面
}
//由于当前数字不能确定是left层还是right层,在这里比较一下
if(((up[i]+(up[i]+i*(left-1)))*left)/2<k) level=right;
else level=left;

搜到第level层之后,我们将k减去((up[i]+(up[i]+i*(level-2)))*(level-1))/2,这时,问题变成了从当前层中,搜索第k个元素。当前层就是从 1 ∼ x 1\sim x 1x,其中x为当前层的代表数。

分组

这个时候还是不能直接从头开始搜,否则还会超时,此时,我们需要将这层分组,如图所示:
在这里插入图片描述
分组在我们最开始分块的时候顺便分出来了,就是num数组,我们通过比较num数组,确定k属于哪一组

for (int j=1; j<=i; j++){
	if(k<=num[j]){
		//找到组
	}
}
分数寻数

找到组后,k减去num[j-1],问题就变成了在这个组中,找第k个元素。由于同组中数字长度相同,因此我们可以再确定k号元素是这个组中哪个数的第几个位置,代码如下,其中用到公式x=(int)log10(number)+1,含义是x是number转化为字符串后的长度。

int remainder=(k%j==0 ? j : k%j);
k=ceil(k*1.0/j*1.0);//第j组内的第k个数
int theNumber=k+pow(10,j-1)-1;
//要的数字是theNumber中的第reminder个数
int temp=(int)log10(theNumber)+1-remainder;
while(temp--) theNumber/=10;
printf("%d\n",theNumber%10);

完整代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <string>
#include <climits>
#include <algorithm>
#include <queue>
#include <vector>
using namespace std;


int q,len;
long long k,sum[20],up[20],down[20],num[20],height=9;
//sum数组存储当前块为止字符数,up,down分别是当前块的上底和下底,num是最长层内分组
int main()
{
    for(int i=1; ;i++){//计算到此块为止字符数
        len=i; num[i]=i*height+num[i-1];
        up[i]=down[i-1]+i;//上底
        down[i]=up[i]+i*(height-1);//下底
        sum[i]=sum[i-1]+((up[i]+down[i])*height)/2;//梯形面积加三角形面积
        if(sum[i]>=1e18) break;
        height*=10;//高
    }

    scanf("%d",&q);
    while(q--){
        scanf("%lld",&k);
        for (int i=1; i<=len; i++){//搜块
            if(k<=sum[i]){//在第i块
                k-=sum[i-1];
                height=((down[i]-up[i])/i)+1;
                long long left=1,right=height,level=0;
                while(left+1<right){//二分层数,搜层
                    long long mid=(left+right)/2;
                    long long temp=((up[i]+(up[i]+i*(mid-1)))*mid)/2;
                    if(temp>k) right=mid;//在上面
                    else left=mid;//在下面
                }
                //由于当前数字不能确定是left层还是right层,在这里比较一下
                if(((up[i]+(up[i]+i*(left-1)))*left)/2<k) level=right;
                else level=left;

                k=k-((up[i]+(up[i]+i*(level-2)))*(level-1))/2;//当前层内第k个数

                //搜索到是level层,开始层内搜组
                for (int j=1; j<=i; j++){
                    if(k<=num[j]){//找到了,在第j组内
                        k-=num[j-1];
                        int remainder=(k%j==0 ? j : k%j);
                        k=ceil(k*1.0/j*1.0);//第j组内的第k个数
                        int theNumber=k+pow(10,j-1)-1;
                        //要的数字是theNumber中的第reminder个数
                        int temp=(int)log10(theNumber)+1-remainder;
                        while(temp--) theNumber/=10;
                        printf("%d\n",theNumber%10);
                        break;
                    }
                }
                break;
            }
        }
    }
    return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值