Luogu P2022 有趣的数
P2022 有趣的数
传送门
题目描述
让我们来考虑1到N的正整数集合。让我们把集合中的元素按照字典序排列,例如当N=11时,其顺序应该为:1,10,11,2,3,4,5,6,7,8,9。
定义K在N个数中的位置为Q(N,K),例如Q(11,2)=4。现在给出整数K和M,要求找到最小的N,使得Q(N,K)=M。
输入格式
输入文件只有一行,是两个整数K和M。
输出格式
输出文件只有一行,是最小的N,如果不存在这样的N就输出0。
输入输出样例
输入 #1
2 4
输出 #1
11
输入 #2
100000001 1000000000
输出 #2
100000000888888879
说明/提示
数据约定
的数据,,;
的数据,,。
题解
参照此篇题解
的意义
由于 是 的位次, 所以有 个数排在其之前.
显然这个题主要讨论的就是有多少个数在 之前, 所以首先将 变成 , 将问题转化.
答案的单调性
对于一个数 , 则对于所有 , 随 的增加而增加 (严格地说是不下降), 所以, 这个题的答案是可以二分的 (很显然不能二分, 因为答案区间很大, 二分的复杂度约为 , 约 , 详见这篇题解)
另辟蹊径
既然不能二分, 寻找此题其他性质, 有 个数排在 前面, 且相同位数的数字是连续出现的. 即排序后, 位数不同的数会排成几个个公差为 的数列, 如
将 前面的数字排序后:
可以看到从 到, , 从 到 是连续的.
所以可以尝试按位数来枚举排在 前面的数.
的意义
由最开始 随 单调递增可得, 对于每一个 , 一定存在
用 存 , 可以判断是否有解, 并且作为求解的第一步.
如何求值, 还是根据本题相同位数连续出现的性质, 按位求.
设 有 位.
对于比 位数少的数, 如果其位数为 , 则设 的前 位组成的 位数为 , 所有 位数中, 小于等于 的数排在 前面, 大于 的数排在 后面.
对于和 相同位数的数, 比 小的数都在 前面, 比 大的数都在 后面.
但是对于 本身也计算进去了, 所以需要再减去 .
得到 的表达式
其中
所以
判断答案存在性
由于 前面的最少有 个数, 所以
, 则答案不存在;
, 则答案就是k;
, 则继续枚举更高位数的数.
求出答案
这时, 已经求出 且 , 从 位数开始枚举.
其实和位数小于 的数相似, 但是不同的是, 此时排在 之后, 可以很简单地写出上面表达式中 的情况.
位数中排在 前面的数的个数
所以只要枚举位数 , 求出 的位数
(由于最高位数的数大概率取不完, 所以这里先算能取完的位数 , 之后再算缺少的数)
这时的 是 也就是 后面补 '', 直到位数达到 .
设上式所求的 前面的数字有 个, 距离要求的 个还差 个.
还差的这些数无疑位数是 , 而且一定是从 开始, 到 结束, 所以最后的答案是
代码
#include <cstdio>
#include <iostream>
using namespace std;
long long m, k, Ten[20], len(0), Min(0);
void getlen(long long x) { //将 len 赋值为 x 的位数
while (x) {
++len;
x /= 10;
}
return;
}
void getmin(long long x) { //将 Min 赋值为 Q(x, x) - 1
for (register int i(1); i <= len; ++i) {
Min += k / Ten[len - i] - Ten[i - 1] + 1;
}
Min--; //去掉 k 本身
return;
}
int main() {
Ten[0] = 1;
for (int i = 1; i < 19; ++i) { //预处理10^i
Ten[i] = Ten[i - 1] * 10;
}
scanf("%lld%lld", &k, &m);
m--;
getlen(k); //求 len
getmin(k); //求 Min
if (Min > m) {
printf("0\n");
return 0;
}
if (Min == m) {
printf("%d\n", k);
return 0;
}
register int lenn(len + 1); //因为要枚举 lenn, 所以 lenn 充当循环控制变量
while (Min < m) { //这时的 Min 代表当前枚举到的排在 k 前面的数的个数
if (lenn >= 20) { //防止答案过大
printf("0\n");
return 0;
}
Min += k * Ten[lenn - len] - Ten[lenn - 1];
++lenn;
}
--lenn; // lenn 从 lenn + 1 变回 lenn
Min -=
k * Ten[lenn - len] - Ten[lenn - 1]; // Min 回到 lenn = lenn - 1 时的情况
printf("%lld\n", Ten[lenn - 1] + m - Min - 1);
// system("pause"); // VSC用户常常忘记删除的一行
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具