版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题目:E2. Numerical Sequence (hard version)
题意:
一串数11212312341234512345612345671234567812345678912345678910……
有q ( q < 500 )次询问,每个询问一个k ( k <= 1e18) ,求该串数第 k 位是什么数字?
解题:
- 求的是第k位数而不是第k个数,9之后数不止一位;
- 这里我们用 sum(x) 表示第 x 块的位数和( 从1~x的位数和), ssum(x) 表示x第一次出现时的位数和:
- 例如 112123 sum(i) 存储 “123” 的位数和 3;
- 而 ssum(x) 表示 第一次出现3(即"112123")的位数和 6; - ssum(x) = ∑sum(x) ;
- 由于 [10i, 10i+1) 的数长度均为 i +1 ,所以这里 每个 [10i, 10i+1) 里的每个sum(i) 就是一个等差数列,差为 i +1 ;
- 所以给定x, ssum(x) = ans[i] + (i+1)∗∑(1,x-i+1) ; ( 10i 为<=x的最大的10的次方数);
- ans(i) = (i+1) ∗ ∑(1,区间长度的和)+ ( sum(10i - 1) - sum(10i-1 - 1)) ∗ (x−10i + 1) + ans(i-1);
- 两次二分就好了,第一次二分找到最大的 x 使 ssum(x) < k; 则解就在 x+1 块里, k −= ssum(x); 第二次二分找到最大的 x 使 sum(x) < k, 那么解就在数 x+1中,k−=sum(x) , 最后数 x+1 的第 k 位就是答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
inline ll get_sum ( ll x ) { //从1~区间长度x的前x项和
return x*(x+1)/2;
}
inline ll calc_1 ( ll x ) { // 获取ssum[x]
ll ans=0, i=1, j=1;
for ( ; j*10<=x; i++,j*=10 ) {
ans += i*get_sum(j*9) + i*j*9*(x-j*10+1);
}
return ans + i*get_sum(x-j+1);
}
inline ll calc_2 ( ll x ) { // 获取sum[x]
ll ans=0, i=1, j=1;
for ( ; j*10<=x; i++,j*=10 ) {
ans += i*j*9;
}
return ans + i*(x-j+1);
}
int main() {
ll q, k;
scanf("%lld", &q);
while (q--) {
scanf("%lld", &k);
ll l = 0, r = 1e9, res1, res2;
while ( l<=r ) { // 第一次二分确定k在哪个块里
ll mid = l+r >> 1;
if ( calc_1(mid) < k ) {
l = mid + 1;
res1 = mid;
}
else r = mid - 1;
}
k -= calc_1(res1);
l = 0, r = res1 + 1;
while ( l<=r ) { // 第二次二分确定k在哪个数里
ll mid = l+r >> 1;
if ( calc_2(mid) < k ) {
l = mid + 1;
res2 = mid;
}
else r = mid - 1;
}
k -= calc_2(res2 ++);
int a[30];
memset(a, 0, sizeof(a));
int i = 0;
while(res2) {
a[++i] = res2%10;
res2 /= 10;
}
printf("%d\n", a[i-k+1]);
}
return 0;
}