时间限制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个输入
,表示询问第
项数字。
Output
输出包含q行
第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块就是第1-9层,第2块就是10-99层,依次类推。
然后我们要用到sum数组,sum[i]记录第 块字符总数量,用梯形面积计算公式可以计算第 块面积(也就是字符个数),使用up[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属于第几层,由于第 块我们将其看作梯形,那么从上底到下底元素个数其实是一个等差数列,公差为 ,我们可以使用二分搜索来搜是第几层。代码如下:
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个元素。当前层就是从 ,其中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;
}