SDU程序设计思维 CSP-M2
C-咕咕东的奇妙序列
Description
Sample
Idea
题意:对于这个无限序列中,查询第k项数字,1<=k<=1e18,考虑到k庞大的数量级,打表时我们采用10的幂次作为每部分,每次查询单独打表。
首先确定用a[x]代表1-X的长度,X∈[1ed,1e(d+1)),a的每次变化遵循等差数列,公差为d+1,用sum[x]代表最开始到第一次出现X的总长度,sum的每次变化遵循等差数列a的求和,ans代表当前的数值X。将X分成个位数,十位数,百位数,…,n位数来计算,即1-9,10-99,100-999,…为每部分。对于给定的X,找到它所在的数量级,根据等差数列公式求出X对应的a,根据等差数列求和公式求出X对应的sum。
有了以上的基础,对于每次查询第k位,先二分查找第一个sum[ans]>=k的ans,那么答案在无限序列中位于从1到ans的的那一段,则k=k-sum[ans-1]定位1-ans这一段,再二分查找第一个a[ans]>=k的ans,则答案就在数值等于ans的那个数中,则k=k-a[ans-1]定位ans这个数,两次查找并更新k后可以确定ans的第k位就是要求的答案。
Summary
这道题比较难,基本思想是二分查找+打表。一开始是考虑先打表后查询,但一个数组不能开到包含1e18个位的数对应的个数,容易造成数组越界,即无法在预处理时存储整个表。因此把打表放进每次查询中避免RE,每次打表至与给定的X同一数量级即可确定X对应的a和sum,二分查找将时间复杂度降至O(logn)避免超时,先定位1-ans这一段,然后定位ans这个数,每次定位更新k。
Codes
#include<iostream>
#include<cstdio>
#include<cmath>
#include<string>
using namespace std;
long long q, k, l, r, mid, ans;
long long solve(long long x, int cho) {
long long pow = 1, sum = 0, a = 0, n = 0, d = 0;
while (true) {
pow *= 10; d++;
if (x > pow - 1) {
//x的数量级大于pow
n = pow - pow / 10;
sum += (a + d)*n + n * (n - 1) / 2 * d;
a += n * d;
}
else {
//x的数量级等于pow
n = x - pow / 10 + 1;
sum += (a + d)*n + n * (n - 1) / 2 * d;
a += n * d;
break;
}
}
return cho == 1 ? sum : a;
}
int main() {
cin >> q;
while (q--) {
cin >> k;
l = 0; r = 1e9;
while (l <= r) {
//寻找第一个>=k的
mid = (l + r) >> 1;
if (solve(mid, 1) >= k) ans = mid, r = mid - 1;
else l = mid + 1;
}
k -= solve(ans-1, 1);
l = 0; r = ans + 1;
while (l <= r) {
//寻找第一个>=k的
mid = (l + r) >> 1;
if (solve(mid, 2) >= k) ans = mid, r = mid - 1;
else l = mid + 1;
}
k -= solve(ans-1, 2);
//第ans个数的第k位
string s = to_string(ans);
printf("%d\n", s[k - 1] - '0');
}
}