题目:
个人思路:
-
读了好多遍题才明白了是怎么回事。。。将数字当成字符来求解第k个字符。eg: 数字 100意思是长度为3的字符串100。
-
在更深入了解之后,我越发觉得这是一道数学题。。通过写数列的变化我们不难看出:
假设 n 代表第 n 组数据: 1、n <= 9时,其是一个公差d = 1的等差数列 2、10 <= n <= 99时,公差d = 2; 3、100 <= n <=999时,公差d = 3; ...... i、10^i <= n <= 10^i-1,公差 d = i+1;
-
然后,我发现,对于某一项来说,或者这么说吧,将数据的组分类别划分:10i-10i+1为划分规则,处于这中间的组为同一类别,因为他们的求和公式相同。
-
又因为对于第n向来说,其合又等于前一项的和加上新增的数,类似于:Sn = Sn-1 + an;所以可以用前缀和来求。
-
剩下的就比较大众化了。因为数据范围的原因,我们不可能对前缀和进行预处理,所以用了二分的方法来找到第k项前的组数。然后第二次二分找到了所在分组的次序。
代码块:
#include <iostream>
#include <string>
#define ll long long
using namespace std;
int q; ll k, pos;
ll get_num(ll x){
ll a = 1, d = 1, n = 0, judge = 10, sn = 0;
for(; judge <= x; d++, judge*=10){
n = judge - judge/10;//项数
sn += a*n+n*(n-1)*d/2;//理解为前缀和,a*n+n*(n-1)*d/2 是第n组的和
a = a +(n-1)*d + (d+1);//下一组的首项
}
ll nn = x - judge/10 + 1;//当前组首项到x的项数
return sn + a*nn+nn*(nn-1)*d/2;
}
ll get_num2(ll x){//找到某一组中x的前面的字符数
ll d = 1, n = 0, judge = 10, sn = 0;
for(; judge <= x; d++, judge*=10){
n = judge - judge/10;
sn += n*d;
}
return sn + (x - judge/10 + 1)*d;
}
int main() {
cin >> q;
for(int i = 0; i < q; ++i){
cin >> k;
ll l = 0, r = 1e9;
int ans = 0;
while(l <= r){//在K所在组前面有多少组
ll mid = (l+r) >> 1;
if(get_num(mid) < k){
l = mid + 1;
ans = mid;
}
else
r = mid - 1;
}
k = k - get_num(ans);
l = 0, r = 1e9;//切记r一定要合适,不然的话后果惨重。
pos = 0;
while(l <= r){//二分查找k在小组中的位置,返回的是第一个小于k所在数的数。。
ll mid = (l+r) >> 1;
if(get_num2(mid) < k){
l = mid + 1;
pos = mid;
}
else
r = mid - 1;
}
string outs = to_string(pos+1);
cout << outs[ k - get_num2(pos) - 1] << endl;
}
return 0;
}
附: