Origin: E2. Numerical Sequence (hard version)
题目描述
咕咕东 正在上可怕的复变函数,但对于稳拿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组询问(1<=q<=500)(1<=q<=500)(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)
输出格式
输出包含 q 行
第i行输出对询问 k i k_i ki 的输出结果。
样例输入
样例输出
数据点 | q(上限) | k(上限) |
---|---|---|
1,2,3 | 500 | 55 |
4,5,6 | 100 | 1 0 6 10^6 106 |
7,8,9,10 | 500 | 1 0 18 10^{18} 1018 |
思路
致命套娃题,当场看到这题…(一层一层往下搜好恶心 ),思路是先定界(定到 k 属于几位数)然后再二分查找(但当时脑子有点混一直在纠结上界取多少还有怎么搜),混着推下去又突然发现把位数之和与数值之和给搞混了,这道题的细节有点多,看到时间不够,无奈改暴力打表水了6个点(其实想想,如果在另一个程序里先打表打到
1
0
18
10^{18}
1018,然后直接赋值,是不是这道题能多偷点分呢2333),下面梳理一下这道题的思路:
-
由等差数列求和公式确定位数边界(即确定第k项属于几位数)
- 设 a [ i ] a[i] a[i] 表示 i i i位数的上界, b [ i ] b[i] b[i]表示最后一个 i i i位数所对应的序列长度, c o u n t count count表示 i i i 位数序列的个数, i i i 表示当前位数
- 1位数: a [ 1 ] = ( 1 + 9 ) × 9 / 2 a[1]= (1+9)×9/2 a[1]=(1+9)×9/2
- 2位数: a [ 2 ] = [ ( 9 + 2 ) + ( 9 + 2 × 90 ) ] × 90 / 2 a[2]=[(9+2)+(9+2×90)]×90/2 a[2]=[(9+2)+(9+2×90)]×90/2
- 3位数: a [ 3 ] = [ ( 9 + 2 × 90 + 3 ) + ( 9 + 2 × 90 + 3 × 900 ) ] × 900 / 2 a[3]=[(9+2×90+3)+(9+2×90+3×900)]×900/2 a[3]=[(9+2×90+3)+(9+2×90+3×900)]×900/2
- …
- i 位数: a [ i ] = [ ( b [ i − 1 ] + i ) + ( b [ i − 1 ] + c o u n t × i ) ] × c o u n t / 2 a[i]=[(b[i-1]+i)+(b[i-1]+count×i)]×count/2 a[i]=[(b[i−1]+i)+(b[i−1]+count×i)]×count/2
-
查找确定 k k k 属于几位数
-
通过二分查找确定 k k k 属于 i i i 位数序列的第几个
-
最后再从这个数所对应的序列里查找 k k k 的位置
-
这道题目的细节(
坑)有点多,部分细节见代码注释
代码实现
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;
typedef long long ll;
ll q,kk,a[20],b[20];
ll Init(){
a[0]=b[0]=0;
ll i=1;
ll count=9;
while(1){
//i位数的上界
a[i]=((b[i-1]+i)+(b[i-1]+count*i))*count/2;
//最后一个i位数所对应的序列长度
b[i]=b[i-1]+count*i;
count*=10;
if(a[i]>(ll)pow(10,18))
break;
i++;
}
return i;
}
void Solve(ll n,ll k){
//确定 k 属于几位数
ll i;
for(i=1;i<=n;i++){
if(k-a[i]<=0) break;
else k-=a[i];
}
//确定 k 属于i位数序列的第几个
ll l=1,r=(ll)(9*pow(10,i-1)),mid;
while(l<=r){
mid=(l+r)>>1;
ll sum=((b[i-1]+i)+(b[i-1]+mid*i))*mid/2;
if(sum>=k) r=mid-1;
else l=mid+1;
}
k-=((b[i-1]+i)+(b[i-1]+r*i))*r/2;
//从这个数所对应的序列中查找 k 的位置
ll len=1;
while(1){
ll sum=9*pow(10,len-1)*len;
if(sum>=k) break;
k-=sum;
len++;
}
//k在第num个len位数中,加上len-1是因为位置可能在一个len位数的中间
ll num=(k+len-1)/len;
//减去前面num-1个len位数,在第num个数中继续搜索位置
k-=(num-1)*len;
//第num个len位数等于几
num=pow(10,len-1)+num-1;
//位于第几位
ll cnt=len-k+1;
for(ll i=1;i<cnt;i++) num/=10;
printf("%lld\n",num%10);
}
int main()
{
scanf("%lld",&q);
ll n=Init();
while(q--){
scanf("%lld",&kk);
Solve(n,kk);
}
return 0;
}
暴力打表(60)
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <map>
#include <algorithm>
#include <string>
#include <cstring>
#include <vector>
#include <cmath>
#include <climits>
using namespace std;
typedef long long ll;
long long a[1000100],kk,t;
void Init_1(){
ll sum=0;
a[0]=0;
int i=1;
while(1){
sum+=log10(i)+1;
a[i]=a[i-1]+sum;
if(a[i]>1000000)
break;
i++;
}
}
void Init_2(){
ll sum = 0;
a[0]=0;
int i=1;
while(1){
sum+=log10(i)+1;
a[i]=a[i-1]+sum;
if(a[i]>(ll)pow(10,18))
break;
i++;
}
}
void Solve(int k){
ll i=1,j=1,sum=0,res;
while(a[i]<k)
i++;
res=k-a[i-1];
sum=0;
for(j=1;j<=i;j++){
sum+=log10(j)+1;
if(sum>=res)
break;
}
if(sum==res)
printf("%lld\n",j%10);
else if(sum>res)
printf("%lld\n",(j/(ll)pow(10,sum-res))%10);
}
int main()
{
scanf("%lld", &t);
bool flag=false;
while(t--){
scanf("%lld", &kk);
if(!flag){
if(kk>1000000)
Init_2();
else
Init_1();
flag=true;
}
Solve(kk);
}
return 0;
}