1.题意
咕咕东 正在上可怕的复变函数,但对于稳拿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 项数字是多少!但是她睡醒之后发现老师讲的东西已经听不懂了,因此她把这个任务交给了你。
2.样例
Input
输入由多行组成。
第一行一个整数q表示有q组询问(1<=q<=500)(1<=q<=500)
接下来第i+1行表示第i个输入k_ik
i ,表示询问第k i项数字。(1<=k_i<=10^{18})
Output
输出包含q行
第i行输出对询问k i的输出结果
Sample Input
5
1
3
20
38
56
Sample Output
1
2
5
2
0
3.解题思路
- 由题意可以看出我们首先应该分部分,首先确定它是第几部分,在确定他在第几部分的第几位数。
- 若用sum表示1-i之间数字的位数,ssum表示第一次出现i时位数,观察可得在1-9时,公差d=1,10-99时,公差d=2,100-999时公差d=3,以此类推,可根据数列求和公式an=am+(n-m)d,sum=na1+n(n-1)/2,分别求出sum与ssum
- 因为数据范围非常大到了1e18,且当数据到第九组即1e8-1e9-1时它的数据范围已经大于1e18,所以采用二分方法l=0,r=1e9,进行两次二分第一次二分求出第一个比它小x则答案在第一次出现x+1之前,然后用k减去ssum,第二次二分求出第一个x的sum小于k的,然后用k-sum[x]则k为数x+1的第k位。
- 将longlong转化为字符串求第k位
- 这个题真的太难了,数学应用以及二分的简化都非常有用!
4.AC代码
#include<iostream>
#include<queue>
#include<vector>
#include<algorithm>
#include<cstring>
#include <strstream>
#include <sstream>
#include <string>
using namespace std;
long long sum,ssum;
string lltoString(long long t)
{
string result;
strstream ss;
ss << t;
ss >> result;
return result;
}
void weishu(long long x)
{
long long mc=1,n=0,d=0;
sum=0,ssum=0;
mc*=10;
while(x>mc-1)
{
d++;
n=mc-mc/10;
ssum+=(sum+d)*n+(n*(n-1))/2*d;
sum+=n*d;
mc*=10;
}
d++;
n=x-mc/10+1;
ssum+=(sum+d)*n+(n*(n-1))/2*d;
sum+=n*d;
}
int main()
{
int q;
cin>>q;
//long long k,l=0,r=1e9,mid;
for(int i=0;i<q;i++)
{
long long temp,k,l=0,r=1e9,mid;
cin>>k;
while(l<=r)
{
mid=(l+r)>>1;
weishu(mid);
if(ssum<k)
{
temp=mid;
l=mid+1;
}
else
{
r=mid-1;
}
}
weishu(temp);
k-=ssum;
l=0,r=mid+1;
while(l<=r)
{
mid=(l+r)>>1;
weishu(mid);
if(sum<k)
{
temp=mid;
l=mid+1;
}
else
{
r=mid-1;
}
}
weishu(temp);
k-=sum;
string str=lltoString(temp+1);
cout<<str[k-1]-'0'<<endl;
}
return 0;
}