C F 1870 F − L a z y N u m b e r s \mathrm{CF1870F - Lazy\ Numbers} CF1870F−Lazy Numbers
D e s c r i p t i o n Description Description
对于给定的 n n n 和 k k k,求解出 1 ∼ n 1\sim n 1∼n 的每一个数在 k k k 进制下字典序排列的顺序,输出满足数字本身为当前排好序后的下标的条件的数的个数( E X : 1 \mathrm{EX:} 1 EX:1 在 1 1 1 位置是满足条件的)。
S o l u t i o n Solution Solution
对于这种
k
k
k 进制的题目,可以想到用 Trie 树进行存储并快速计算,不过该题的 Trie 树的建树方式为由高位往低位建图,且不用补前导
0
0
0。(如图所示,此时为
n
=
9
,
k
=
2
n=9,k=2
n=9,k=2 时的建图情况)
由于要以字典序排列,故采用每一个点下面的边也按由小到大排列。这张 Trie 树的图就记录 1 ∼ n 1\sim n 1∼n 中的每一个数,其中字典序的顺序其实就是 DFS 的顺序,即一个点在按字典序排序之后的下标即为在 DFS 序中的下标。因为每一个点对应的边都是从小到大排列的,所以左侧的点一定字典序小,越往右字典序更大。
之后可以发现在同一层中,相邻的点一定相差 1 1 1,但是 DFS 序一定相差大于 1 1 1,故在第 i i i 层中,相差为 0 0 0 的一定是某一连续的段,且差值是满足单调性的,故可以二分。
那么接下来的问题就是如何求出 DFS 序:不可能进行一次 DFS 做一遍,因为 n n n 是达到 1 0 18 10^{18} 1018 级别的,必定 TLE。但是,可以发现求某个数的 DFS 序可以转化为求有多少个数在他之前访问。
对于转化后的问题,就好求多了,对于每一层,计算出位于这个数左边的数的个数:
对于蓝色的数字个数,就是 6 6 6 在每一层时所对应的数的左边的数(包含该数),例如 6 6 6 在第 2 2 2 层所对应的数为 3 3 3,也就是有根节点到 6 6 6 号点所组成的链中的第 2 2 2 层的点。这个可以考虑 6 6 6 在 k k k 进制的前缀来计算,比如说 k = 2 k=2 k=2,那么二进制是 110 110 110:第一个前缀为 1 1 1,十进制为 1 1 1,故第二层所对应的数为 1 1 1;第二个前缀为 11 11 11,十进制为 3 3 3,故第二层所对应的数为 3 3 3;第三个前缀为 110 110 110,十进制为 6 6 6,故第三层对应的数为 6 6 6。
由于在 k k k 进制下第 i i i 层最左侧的数为 k i k^i ki,其中 i i i 从 0 0 0 开始,即 1 1 1 的那一层为第 0 0 0 层。那么这一层比他小的数为:前缀 i − k i + 1 i - k^i+1 i−ki+1。比如说,第 2 2 2 层( 6 6 6 的那一层),就应该是 6 − 2 2 + 1 = 3 6-2^2+1=3 6−22+1=3。
这样,就可以快速的求解出蓝色部分的值了,但是还有绿色部分也比 6 6 6 的 DFS 序小,所以如果未到这棵树的最后 1 1 1 层,可以通过尾部不断加入 0 0 0 来继续计算(计算方式与上文一致),不过在最后一层要特别注意,可能是不满的,所以此时不能再用前缀减 k i k^i ki 了,而是用 n n n 减 k i k^i ki 加 1 1 1。例如, 6 6 6 在末尾加 0 0 0 直到达到整棵树的深度: 1100 1100 1100,这时候在第 3 3 3 层时,不能直接用当前数减 8 8 8 加 1 1 1,而是用 9 9 9,因为 9 9 9 到 12 12 12( 1100 1100 1100)之间的数是没有的。注意:这里 12 12 12 不能算入比 6 6 6 小的因为这是方便计算加入的 0 0 0 才出现的数,不能计入贡献。
最后拿 3 3 3 给大家展示一下全过程:
-
蓝色部分:
(1)第 0 0 0 层: 1 1 1(二进制 1 1 1) − 2 0 + 1 = 1 - 2^0+1=1 −20+1=1
(2)第 1 1 1 层: 3 3 3(二进制 11 11 11) − 2 1 + 1 = 2 -2^1+1=2 −21+1=2
-
绿色部分:将 3 3 3 补完 0 0 0 后得到 1100 1100 1100,数字为 12 12 12
(1)第 2 2 2 层: 6 6 6(二进制 110 110 110) − 2 2 + 1 − 1 = 2 -2^2+1-1=2 −22+1−1=2(注意不能包含 6 6 6)
(2)第 3 3 3 层: 9 − 2 3 + 1 = 2 9-2^3+1=2 9−23+1=2(最后 1 1 1 层用 n n n 减)
最后将贡献相加: 1 + 2 + 2 + 2 = 7 1+2+2+2=7 1+2+2+2=7, 3 3 3 的 DFS 序为 7 7 7 位置。
至此,该题目就可以在
O
(
log
2
n
log
k
2
n
)
O(\log_2n\log_k^2n)
O(log2nlogk2n) 的时间复杂度通过该题目了,注意某些位置要开 __int128
,不过尽量减少使用,详见代码。
C o d e Code Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
typedef __int128 i128;
int N, K, L, Pow_K[100], KX[100], S1, S2;
int DFS(int X)
{
int Result = 0; i128 DEC = 0; S1 = 0, S2 = 0;
while (X) KX[S1 ++] = X % K, X /= K;
reverse(KX, KX + S1);
for (int i = 0; i < S1; i ++) DEC = DEC * K + KX[i], Result += DEC - Pow_K[i] + 1;
for (int i = S1; i < L; i ++) DEC = DEC * K, Result += min(DEC - 1, (i128)N) - Pow_K[i] + 1;
return Result;
}
void solve()
{
cin >> N >> K;
i128 T = 1; int M = 0, Cpy = N; L = 0, Pow_K[0] = 1;
while (T * K <= (i128)N) T *= K, M ++, Pow_K[M] = T;
while (Cpy) Cpy /= K, L ++;
int Result = 0;
for (int i = 0; i < L; i ++)
{
int l = Pow_K[i], r = Pow_K[i + 1] - 1, l2 = Pow_K[i], r2 = min(Pow_K[i + 1] - 1, N);
if (i == L - 1) r = N, r2 = N;
while (l < r) DFS(l + r >> 1) - (l + r >> 1) < 0 ? l = (l + r >> 1) + 1 : r = (l + r >> 1);
while (l2 < r2) DFS((l2 + r2 + 1 >> 1)) - (l2 + r2 + 1 >> 1) > 0 ? r2 = (l2 + r2 + 1 >> 1) - 1 : l2 = (l2 + r2 + 1 >> 1);
if (DFS(l) != l) continue;
Result += r2 - l + 1;
}
cout << Result << '\n';
}
signed main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
int Data;
cin >> Data;
while (Data --)
solve();
return 0;
}
F i n a l W o r d s Final\ \ Words Final Words
- 题目细节很多,还要注意减少
__int128
的使用,写代码的时候要耐心写 - 这道题目值得大家取仔细思考,比如说一开始我写了一个 O ( log 2 n log k 3 n ) O(\log_2n\log_k^3n) O(log2nlogk3n) 的复杂度,之后 TLE 了,才改成 log k 2 n \log^2_k n logk2n 的。
一张坎坷 A C \bold{\color{green}{AC}} AC 历程给大家展示
- 这道题目主要考察了 Trie 树,却没有真正的需要写 Trie 树,而是通过二分与数学推导的结合来实现这到题目,可谓是及其的妙!
- 也是本蒟蒻做过不多的紫题中的一道,研究了 2 2 2 天,在此纪念一下~~~