题目
思路
我觉得这道题非常难,一开始我完全找不到思路,只能尽量的骗分,后来问了别人才明白怎么做。本题最核心的是两个数组,ret1和ret2。ret2[i]保存的是从1到最大的i位数的子串的位数,做法是ret[i-1]+i位的所有数的位数,比如ret[2],即1-99的位数,就是1-9的位数(ret[i])加上10-99的位数,(99-9)*2([pow(10,i)-pow(10,i-1)]*i)。ret1[i]中保存的的是从开始到i位数的最大数字开始出现时的位数,1-9为45,1-99为9045(方法是用等差数列求和的方式,这也是本题最核心的一个算法之一,另一个是二分法)。方法不好说,举个例子讲一下9045是怎么求的(即ret1[2]),先算上ret1[1],然后剩下的90×9(1-9九位数),然后一个从10,1011,101112…,101112…99的等差数列求和即可。其他的实现我都给了详细的注释,就是一个挺复杂的数学问题,但是易出错。
代码
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#define inf 1e18
using namespace std;
typedef long long ll;
ll ret1[10];
ll ret2[10];
ll ret(ll t) { //求从1开始直到t的位数(包括t)
ll tt = t;
ll r1=0; //求t是几位数,虽然r1不会太大但是要和r2做运算所以还是把它设为ll型
while (tt>0) {
r1++;
tt/=10;
}
ll r2=0;
for (ll i = 1;i<r1; i++) {
r2+=(pow(10, i) - pow(10, i - 1)) * i;
}
r2+=(t-pow(10,r1-1)+1)*r1;
return r2;
}
void Query(){
int q;
cin>>q;
for (int i=0;i<q;i++) {
int k;
cin>>k;
int time;
for (int j = 1; j <= 9; j++) {
if (ret1[j] >= k) {
k-= ret1[j-1];
time=j;
break; //找到k在最高几位数的分组中,time记录位数,并且k现在是从pow(10,j-1)开始的名次
}
}
//我们以time为2为例,即从10-99
ll l = 1;
ll r = pow(10,time) - pow(10, time-1); //10到99一共90个二位数
ll mid;
ll rt=ret(pow(10, time - 1)); //1-10一共多少位
ll rnk=0;//记录mid=r+l/2的名次
//二分
while (l <= r) {
mid = (l + r) / 2;//第一次是45
rnk= mid*rt+mid*(mid-1)*time/2;//45个rt表示从1-10开始的组到1-45开始的组每一个都有1-10,剩下的就是一个等差数列求和乘以位数
if (rnk >= k) r = mid - 1;//mid的名次大了
else l = mid + 1;
}
//可能在 mid-1,mid,mid+1组中
ll t1=(mid-2)*rt+(mid-2)*(mid-3)*time/2; //mid-1前面的位数
ll t2= (mid-1)*rt + (mid-1)*(mid-2)*time/2; //mid
ll t3= mid *rt + (mid-1)*mid*time/2; //mid+1
if(k<=t2){
k-=t1;
}
else
{
if(k<=t3)
{
k-=t2;
}
else
{
k-=t3;
}
}
for (int j = 1; j <= 9; j++) {
if (ret2[j] >= k) {
k -= ret2[j - 1]; //现在只需要从1-mid里面找了,先把低位数的减去这里是把一位数的减去从全部二位数里面找
time=j;
break;
}
}
ll m1=k/time; //有几个time位数
ll m2=k%time; //k在time位数的第几位
if (m2==0) {//在最后一位即个位,求除以10的余数即可
ll re=pow(10,time-1)+m1-1;
cout<<re%10<<endl;
}
else {
ll re=pow(10, time- 1) + m1;//还是算出这个数先
ll remain=time-m2;//k后面的time位数的位数
re=re/pow(10,remain);//相当于把k后面的位数舍去不看,比如145要想取得4,用145/10得到14,求余取个位即可
cout<<re%10<<endl;
}
}
}
int main() {
ret2[0]=0;
for (ll i = 1; i <= 9; i++) {
ret2[i] = ret2[i - 1] + (pow(10, i)-pow(10, i - 1))*i;
}
ret1[0]=0;
for (ll i = 1; i <= 8; i++) {
ll n=9*pow(10,i-1);
ret1[i]=n*(n+1)*i/2+n*ret2[i-1]+ret1[i - 1];//
}
ret1[9]=inf;
Query();
return 0;
}