题目
给定一个数列,1 12 123 1234,无限往后延伸,到10的时候10算两个字符,以此类推
q(q<=500)个询问,第i次询问第ki个字符是什么
①easy version: ki<=1e9
②hard version: ki<=1e18
题解
相同的思想是,先二分去掉二重前缀,确定k落在一个1到x的段里,把1到x-1前面所有段都减掉
再二分一次去掉单重前缀,确定k落在[1,x]段的y值上,把1到y-1前面所有值都减掉
最后用sprintf把y值搞进字符串,输出第k位即可
Easy:
天下好题一大抄,UVA10706原题警告
考虑到N大概到3W多,就会出现1e9字符,所以可以按值做
统计a[i]统计1到x需要多少字符,sum[i]对a[i]求前缀和,先二分sum再二分a最后输出第k个值
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=35000;
int q,k,pos;
ll a[N],sum[N];
char s[11];
void init()
{
for(int i=1;i<N;++i)
{
a[i]=a[i-1]+log10(i)+1;
sum[i]=sum[i-1]+a[i];
}
}
int main()
{
init();
scanf("%d",&q);
while(q--)
{
scanf("%d",&k);
pos=lower_bound(sum,sum+N,k)-sum;
k-=sum[pos-1];
pos=lower_bound(a,a+N,k)-a;
k-=a[pos-1];
sprintf(s+1,"%d",pos);
printf("%c\n",s[k]);
}
return 0;
}
Hard:
由于k到1e18,所以大概值大概到1e9,上述方法不可取,考虑按位做
L[i],R[i]分别确定i位的数的左右界,左闭右开,如L[2]代表10,R[2]代表100
l[i],r[i]用于确定i位的数最小和最多需要多少位,如l[2]代表1到10的位数和,r[i]代表1到99的位数和
sum[i]代表小于等于i位的所有的字符的个数和,sum[2]代表1 12 ...到以99结尾的字符和
多了一次对位数的分类讨论,先枚举出所在段[1,left]的位,减去前面比left小的位的和
再用等差数列搞掉和left相等的位的和,然后说明k落在[1,left]里的值上,
此时令right=left,left=1,再开始二分y值,把[1,y-1]的值都减掉
减掉的方法,可以纵向统计每个位出现的个数,在cal函数里
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int q;
ll sum[10],l[10],r[10],L[10],R[10],k,ans,lef,rig;
char s[11];
void init()
{
ll now=1;
for(int i=1;i<=9;++i,now*=10)
{
L[i]=now;R[i]=10*L[i];//[L[i],R[i])
l[i]=r[i-1]+i;r[i]=r[i-1]+(R[i]-L[i])*i;
sum[i]=sum[i-1]+(l[i]+r[i])*(R[i]-L[i])/2;
}
}
ll cal(ll mid)
{
ll v=0;
for(int j=1;j<=9;++j)
{
if(mid>=L[j])v+=mid-L[j]+1;
else break;
}
return v;
}
int main()
{
init();
scanf("%d",&q);
while(q--)
{
scanf("%lld",&k);
int i;
for(i=1;i<=9;++i)
if(sum[i]>=k)break;
k-=sum[i-1];
lef=L[i],rig=R[i];
while(lef<=rig)
{
ll mid=(lef+rig)/2,v=l[i]+(mid-L[i])*i;
if((l[i]+v)*(mid-L[i]+1)/2>=k)rig=mid-1;
else lef=mid+1;
}
k-=(l[i]+l[i]+(lef-1-L[i])*i)*(lef-L[i])/2;
rig=lef,lef=1;
while(lef<=rig)
{
ll mid=(lef+rig)/2;
if(cal(mid)>=k)rig=mid-1;
else lef=mid+1;
}
k-=cal(lef-1);
sprintf(s+1,"%lld",lef);
printf("%c\n",s[k]);
}
return 0;
}
心得
hard是自己莽过去的,大概学了一下easy的便捷做法,
log(10)+1统计位数,sprintf的用法等等