题目链接:http://codeforces.com/contest/1216/problem/E2
题意:有一个无限长的数字序列,其组成为1 1 2 1 2 3 1.......1 2 ... n...,即重复的1~1,1~2....1~n,给你一个k,求第k(1e18)个数字是什么
分析:首先,我们先来大致算一下大概有多少个块(一个块值一次1到i)我们假设第i块的长度为i(事实上要更大,因为到了后面,有两位数,三位数...),这样算出来,前n块的和要达到1e18的话,n大致为1e9(等差数列求和是平方的复杂度),我们就必须找到一个较快的速度找到它属于第几块
因为前i+1块的和肯定是大于前i块的和的,也就是说单增,我们可以用二分来处理这个问题,具体看代码
几个难懂的地方:
add值的是比当前长度短的数的和,比如当长度为3时,所有长度为2的数的长度和为189(1-9,10-99)
cnt*(cnt+1)/2*len是因为我们我们求的是1个当前长度,2个当前长度的数一直到cnt个当前长度的数,也就是等差数列求和,然后再乘个len就好
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=2e5+7; const int N=1e3+7; const int inf=0x3f3f3f3f; ll k; ll get(ll x){ return x*(x+1)/2; } ll sumto(int x,int need){ ll sum=0; ll add=0; ll p=1; for(int len=1;;len++){ if(p*10-1<x) { ll cnt=p*10-p;//长度为Len的数字的个数 if(need){ //get(cnt)*len是长度为len的数字的总长度,但因为每个数字都是从1开始的,前面还有长度为1,2..len-1的总和,这个就是add保存的 //一共有cnt*add个需要加 sum+=cnt*add+get(cnt)*len; add+=cnt*len;//每次add存的都是比当前长度小的所有数字的长度和 } else sum+=cnt*len; } else { ll cnt=x-p+1; if(need){ sum+=cnt*add+get(cnt)*len; } else sum+=cnt*len; break; } p*=10; } return sum; } int main(){ int q;scanf("%d",&q); while(q--){ scanf("%I64d",&k); int l=1,r=1e9; int res=-1;//求的是第一个和比k大的模块 while(l<=r){ int mid=l+r>>1; if(sumto(mid,1)>=k){ res=mid; r=mid-1; } else l=mid+1; } //cout<<res<<endl; //cout<<sumto(res-1,1)<<endl; k-=sumto(res-1,1); l=1,r=res; int ans=-1; while(l<=r){ int mid=l+r>>1; if(sumto(mid,0)>=k){ ans=mid; r=mid-1; } else l=mid+1; } //cout<<ans<<" "<<k<<endl; k-=sumto(ans-1,0); //cout<<k<<endl; cout<<to_string(ans)[k-1]<<endl; } return 0; }