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

时间限制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 ,表示询问第 k i k_i 项数字。 ( 1 < = k i < = 1 0 18 ) (1<=k_i<=10^{18})

Output

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

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 ,第三部分是 1 3 , 1\sim 3,\cdots\cdots ,因此,第 i i 部分就是 1 i 1\sim 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 。如下图所示:
在这里插入图片描述
其中,第1块就是第1-9层,第2块就是10-99层,依次类推。

然后我们要用到sum数组,sum[i]记录第 1 i 1\sim i 块字符总数量,用梯形面积计算公式可以计算第 i i 块面积(也就是字符个数),使用up[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 ,我们可以使用二分搜索来搜是第几层。代码如下:

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 ,其中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;
}
发布了45 篇原创文章 · 获赞 39 · 访问量 4533

猜你喜欢

转载自blog.csdn.net/weixin_43347376/article/details/105390205