题解
这是一道类似于前缀和的题,但是由于数据范围太大,我们不可能对前缀和进行预处理存到数组中,只能在用到某项的前缀和时临时计算。
我们可以把这个奇妙序列看做许多部分,1
,12
,123
,1234
,12345
,123456
…(第n部分是数字1~n的所有字符的排列)当我们要求第n项的字符时,先求它位于这个奇妙序列的哪一部分,因为数据很大,我们用二分法来求。
求出第n个字符位于哪一部分之后,用n减去前面部分的字符之和,就变成了求这一部分的第n个字符是什么。这一部分也有可能很长,所以我们也用了二分法来求。
求出第n个字符位于这一部分的第几个数字之后,用n减去这一部分前面的字符之和,就变成了求这一个数字的第n个字符是什么。我们直接使用to_string函数,求出第n个字符。
两次使用二分法找出前缀和小于n的最后一个点。第一次计算前缀和时(计算前面所有部分的字符数目),因为前面的部分类似于多个等差数列,我们用了等差数列的方法来求(实际比等差数列复杂)。
最后,csp模拟结束后又想到,如果数据太大又时间紧迫临时想不到好的处理方法的话,可以用简单的做法写一下,说不定可以过前几个数据小的点。
代码
#include <iostream>
#include <string>
using namespace std;
int q; long long k;
long long block(long long x) {
long long d = 1, i = 1, n = 0, j = 10, ret = 0;
for (; j <= x; i++, j *= 10) {
n = j - j / 10;
ret += d * n + n * (n - 1) * i / 2;
d = d + (n - 1) * i + (i + 1);
}
long long tmp = x - j / 10 + 1;
return ret + d * tmp + tmp * (tmp - 1) * i / 2;
}
long long numbe(long long x) {
long long i = 1, n = 0, j = 10, ret = 0;
for (; j <= x; i++, j *= 10) {
n = j - j / 10;
ret += n * i;
}
return ret + (x - j / 10 + 1) * i;
}
int main() {
cin >> q;
for (int i = 0; i < q; ++i) {
//cout << "jinlaile" << endl;
cin >> k;
long long l = 0, r = 1000000000;
long long ans = 0;
while (l <= r) {
long long mid = (l + r) >> 1;
if (block(mid) < k) {
l = mid + 1;
ans = mid;
}
else
r = mid - 1;
}
k = k - block(ans);
l = 0, r = 1000000000;
long long answ = 0;
while (l <= r) {
long long mid = (l + r) >> 1;
if (numbe(mid) < k) {
l = mid + 1;
answ = mid;
}
else
r = mid - 1;
}
k = k - numbe(answ);
string str = to_string(answ + 1);
cout << str[(int)k - 1] << endl;
}
return 0;
}