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

说明/提示

数据约定

40%的数据,1<=K,M<=105

100%的数据,1<=K,M<=109

题解

参照此篇题解

m 的意义

由于 mk 的位次, 所以有 m1 个数排在其之前.

显然这个题主要讨论的就是有多少个数在 k 之前, 所以首先将 m 变成 m1, 将问题转化.

答案的单调性

对于一个数 k, 则对于所有 nk, Q(n,k)n 的增加而增加 (严格地说是不下降), 所以, 这个题的答案是可以二分的 (很显然不能二分, 因为答案区间很大, 二分的复杂度约为 O(N(log2N)2), 约 1093121012, 详见这篇题解)

另辟蹊径

既然不能二分, 寻找此题其他性质, 有 n1 个数排在 k 前面, 且相同位数的数字是连续出现的. 即排序后, 位数不同的数会排成几个个公差为 1 的数列, 如 Q(11,9)=11

{1,10,11,2,3,4,5,6,7,8,9}

9 前面的数字排序后:

{1,2,3,4,5,6,7,8,10,11,9}

可以看到从 1 到, 8, 从 1011 是连续的.

所以可以尝试按位数来枚举排在 k 前面的数.

Min 的意义

由最开始 Q(n,k)n 单调递增可得, 对于每一个 k, 一定存在

Q(k,k)Q(n,k) , (nk)

MinQ(k,k)1, 可以判断是否有解, 并且作为求解的第一步.

如何求值, 还是根据本题相同位数连续出现的性质, 按位求.

klen 位.

对于比 k 位数少的数, 如果其位数为 i(ilen), 则设 k 的前 i 位组成的 i 位数为 ki, 所有 i 位数中, 小于等于 ki 的数排在 k 前面, 大于 ki 的数排在 k 后面.

对于和 k 相同位数的数, 比 k 小的数都在 k 前面, 比 k 大的数都在 k 后面.

但是对于 k 本身也计算进去了, 所以需要再减去 1.

得到 Min 的表达式

Min=(i=1lenki10i1+1)1

其中 ki=k10leni

所以

Min=(i=1lenk10leni10i1+1)1

判断答案存在性

由于k 前面的最少有 Min 个数, 所以

k<Min, 则答案不存在;

k=Min, 则答案就是k;

k>Min, 则继续枚举更高位数的数.

求出答案

这时, 已经求出 MinMin>m, 从 len+1 位数开始枚举.

其实和位数小于 len 的数相似, 但是不同的是, ki 此时排在 k 之后, 可以很简单地写出上面表达式中 i>len 的情况.

i 位数中排在 k 前面的数的个数

k10ilen10i1

所以只要枚举位数 i, 求出 n 的位数 lenn

(由于最高位数的数大概率取不完, 所以这里先算能取完的位数 lenn1, 之后再算缺少的数)

i=1lenn1{k10ilen10i1+1  (i<len)k10ilen10i1  (ilen)}<m

这时的 nk10lennlen1 也就是 k 后面补 '0', 直到位数达到 lenn1.

设上式所求的 k 前面的数字有 Q 个, 距离要求的 m 个还差 mQ 个.

还差的这些数无疑位数是 lenn, 而且一定是从 10lenn1 开始, 到 10lenn1+mQ1 结束, 所以最后的答案是

n=10lenn1+mQ1

代码

#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;
}
posted @   Wild_Donkey  阅读(154)  评论(0编辑  收藏  举报
编辑推荐:
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示