题目描述
格式说明
样例输入
样例输出
数据规模
思路
由题知,这个无限序列的第i部分是从1~i的子序列,该解法的大体思路是我们首先确定要查询的项(设为k项)在无限序列的第几部分(第几个子序列),然后再从这部分序列里找到是哪个数的第几位即可。
首先,我们需要知道如何计算子序列的项数以及总的无限序列的项数。假设无限序列中x第一次出现,则x必属于第x部分的结尾,若x为d位数,设sum(x)为第x部分子序列的项数。第x-1部分可得为sum(x-1)=sum(x)-d;故可得当x位数为d时,sum(x)是一个公差为d的等差数列。
则总的项数和可用等差数列前n项和求取(当位数为d时,等差数列的总项数为9*pow(10,d-1),首项为位数为d-1位时最后一项+d)。
不过这里要注意每当x位数变化时,公差会发生变化。所以我们可以把该无限序列子序列项数看作多个公差不同的等差数列的集合。给定一个x,我们可以在确定x的位数的同时利用等差数列的性质求取下一个等差数列的首项(公差为x的位数),同时将不同等差数列项数和加起来以确定无限序列的总项数。
最后,我们采用二分的办法,求解x第一次出现时总项数小于k,但x+1出现时项数大于k的x,此时k项出现在第x+1部分中。用k减去x出现时的总项数得到的项数k2即为目标项在子序列x+1部分里的项数。
然后继续二分这一部分,求解该子序列中某一数x出现时子序列项数小于k2,但x+1出现时子序列项数大于k2,用k2减去x出现时子序列项数得到k3,即为目标项在x+1从高位数起时的项数,最后输出该项即为最终答案。
反思
1、在确定了k项属于无穷序列第几部分后,原来采用了对该项序列一一遍历的思想来求取x,使得运行超时,后来想到用两次二分来降低时间复杂度。
2、由于数据规模比较大,整型数据并不能满足题目要求,使用长整型后计算每一个等差序列的项数(x为n位数时,所有等差数列项数为9*pow(10,n-1))时使用了pow()函数,函数返回值向长整型转化时由于精度不同导致项数计算出现了错误,因此在函数中使用另一个长整型变量记录当前等差数列项数。
代码
#include <iostream>
#include <string>
#include <cmath>
using namespace std;
long long getsum1(long long x)
{
long long temp=x;
long long ans=0,sum=0,d=0,cnt=1;
while(temp>=10)
{
cnt*=10;
d++;
long long n=cnt-cnt/10;
ans+=n*(sum+d)+n*(n-1)/2*d;
sum+=n*d;
temp/=10;
}
d++;
long long n=x-cnt+1;
ans+=n*(sum+d)+n*(n-1)/2*d;
return ans;
}
long long getsum2(long long x)
{
long long temp=x;
long long ans=0,d=0,cnt=1;
while(temp>=10)
{
cnt*=10;
d++;
long long n=cnt-cnt/10;
ans+=n*d;
temp/=10;
}
d++;
long long n=x-cnt+1;
ans+=n*d;
return ans;
}
int main()
{
int n;
scanf("%d",&n);
while(n--)
{
long long q;
scanf("%lld",&q);
long long ans=0,left=0,right=1e9;
while(left<=right)
{
long long mid=(left+right)/2;
long long sum=getsum1(mid);
if(sum<q)
{
ans=mid;
left=mid+1;
}
else
{
right=mid-1;
}
}
long long sum=getsum1(ans);
q-=sum;
left=0,right=ans+1,ans=0;
while(left<=right)
{
long long mid=(left+right)/2;
long long sum=getsum2(mid);
if(sum<q)
{
ans=mid;
left=mid+1;
}
else
{
right=mid-1;
}
}
sum=getsum2(ans);
q-=sum;
ans++;
string str=to_string(ans);
printf("%c\n",str[q-1]);
}
return 0;
}