『题解』CodeForces CF1177B Digits Sequence (Hard Edition)
题目大意
输出 \(1234567891011121314151617\dots\) 的第 \(k(1 \le k \le 10^{12})\) 项。
思路
考虑直接模拟,\(\mathcal{O}(k)\) 时间复杂度建立这个序列,并输出第 \(k\) 项。显然,这是不可行的方法,数据范围为 \(10^{12}\)。但是CF1177A可以。
那么我们就要考虑更快的方法:找出包含第 \(k\) 项的那个数。
确定位数
首先,需要确定这个数的位数。
先来观察一下位数分别为 \(1,2,3,4,5\dots\) 位的数分别有多少个:
- \(1\) 位:\([1 \dots 9]\),\(9\) 个数。
- \(2\) 位:\([10 \dots 99]\),\(90\) 个数。
- \(3\) 位:\([100 \dots 999]\),\(900\) 个数。
- \(\dots\)
但是,我们要求的是这些数合在一起,就像字符串拼接一样,一共有多少位。很简单,将不同位数的数的数量乘上它们各自的位数,就是一共的位数了。
- \(1\) 位:一共 \(9\) 个数,\(9\) 位。
- \(2\) 位:一共 \(90\) 个数,\(180\) 位。
- \(3\) 位:一共 \(900\) 个数,\(2700\) 位。
- \(\dots\)
设位数为 \(len\),那么当前位数的数的总位数计算公式为:\(len \times 10^{len-1} \times 9\)。
得到这些结论,那么得出到底是几位数就很简单了。
进行一个循环,定义 \(len=1\),\(len\) 每次加一,我们每次将 \(k\) 减去有 \(len\) 位的数的总位数,直到发现再减就比 \(0\) 小了为止。
\(len\) 最后的值即为包含第 \(k\) 位数的数的位数。
怎么这么绕口令啊。
代码实现:
ll i,len; // 用 i 存放 10^len-1
// 只要序列第 k 位不在当前位数的数里,那就继续减,直到找到为止
for(i=1; k-len*i*9>=0; len++,i*=10){
k-=len*i*9; // 减去当前位数的数的总长度,下一次要找比当前多一位的
}
确定包含第 \(k\) 位的数是多少
我们定义包含第 \(k\) 位的数为 \(n\)。
最基本的,就是将位数 \(\le len\) 的数的个数都加进去,这样就找到了这个数的最小值。这个操作要和上面的找位数一起执行
如何准确确定这个数是多少呢?
在我们找到这个数的最小值后,\(k\) 成为了序列上从有 \(len\) 位数开始的第 \(k\) 项。\(len\) 是位数,那么 \(n+\dfrac{k}{len}\) 就是包含第 \(k\) 位的数。
例如 \(len=3\),经过操作后的 \(k=6\),那么 \(n\) 就应该是 \(\dots 100101102103 \dots\) 中的 \(101\),也就是 \(99+\dfrac{3}{6}\)。
应该很容易理解吧,就是要求第 \(k\) 位,\(k\) 除以 \(len\) 就代表有多少个三位数在 \([k,len]\) 之间,就这么简单。
还有一个问题,就是——
若 \(k \bmod len \ne 0\)?
上面只考虑了最基本的情况,这里需要加上一个特判。
还是一个例子:\(len=3,k=5\),截取的序列为 \(\dots 100101102103 \dots\),尝试使 \(n \gets n+\dfrac{k}{len}\),C++
有自动向下取整,所以得到的结果为 \(100\)。显然,包含第 \(k\) 位的数并不是 \(100\),而是 \(100+1=101\)。
在 \(n \gets n+1\) 后,答案也有所变更。我们可以通过式子 \(len-(k \bmod len)\) 得出答案在哪一位,设 \(len-(k \bmod len)\) 为 \(bit\),那么我们将 \(n\) 的后 \(bit-1\) 位去掉即可。
处理后,\(n\) 的个位就是答案。
代码
代码里注释也很详细的(可以说是差不多又讲了一遍)。
此代码也可过CF1177A。
#include <iostream>
using namespace std;
typedef long long ll;
// n 存放第 k 个数字所在的数,用于计算最后的答案
// len 存放 k 所在数的位数
// bit 用于存储序列第 k 位在 n 中从右往左数哪一位
ll k,n,len=1,bit;
int main(){
// k 存放的是要求序列的位数,我们循环时直接让它减去已经跳过的位
cin >> k;
// 越到高位,包含的数越多(废话)
// 只要序列第 k 位不在当前位数的数里,那就继续减,直到找到为止
for(ll i=1; k-len*i*9>=0; len++,i*=10){
k-=len*i*9; // 减去当前位数的数的总长度,下一次要找比当前多一位的
n+=i*9; // 计算序列第 k 位所在的数
}
// 经过上面的处理后的 k 就是序列从有 len 位数开始的第 k 项
// 就比如 len=3,那么现在的答案就是 100101102103... 的第 k 项
// 那么 k/len 就是从 len 位数开始,经过了多少个数才到包含第 k 项的那个数
// 于是包含 k 的那个数 n 加上 k/len 就正好是包含 k 的那个数了
n+=k/len;
// 还有一个问题:k/len 除不尽的情况
// 如果 k/len 刚好除的尽,那么答案就是这个数的个位,应该很容易理解吧
// 若除不尽,万(hěn)能(dú)的 C++(C cǎo) 会向下取整
// 那就说明,现在的 n 还不是包含 k 的那个数,n+1 才是包含 k 的数
// 例如,执行完上面代码的 k=5,len=3,n=100
// 在 ...100101102... 中,显然,n 应该是 101 才对
// 所以就要加一个特判
if(k%len!=0){
n++; // 转移到下一个数
bit=len-(k%len);
// 我们还要将 n 的个位变为答案
// 可以通过 len-(k%len) 得出最终答案在 n 的从右往左数第几位
// 然后,将 n 后面 bit 位都去掉,就得到最终答案;n 的第一位了
while(bit-->0){
n/=10;
}
}
cout << n%10 << endl; // 剩下数的最后一位即为答案
return 0;
}
代码里是不是好多注释都是废话?