小米OJ - 找到第 N 个数字 II - 等差数列求和及其通项然后二分

探讨如何快速定位由连续递增数字组成的无限长字符串中任意位数的数字,通过数学分析与二分查找算法,实现高效求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

链接Here

假如有一组字符串符合如下规律:

S 1 = 1 S 2 = 12 S 3 = 123 S 4 = 1234 ⋯ S 9 = 123456789 S 10 = 12345678910 ⋯ S 18 = 123456789101112131415161718 \begin{array}{} S_{1} =1\\ S_{2} =12\\ S_{3} =123\\ S_{4} =1234\\ \cdots \\ S_{9} =123456789\\ S_{10} =12345678910\\ \cdots \\ S_{18} =123456789101112131415161718 \end{array} S1=1S2=12S3=123S4=1234S9=123456789S10=12345678910S18=123456789101112131415161718

(对于 S n S_n Sn来说,将从 1 1 1 n n n的数字拼接到一起)

现在我们把所有的字符串拼接起来,组成一个无限长的字符串 S = 1121231234 ⋯ 12345678910111213 ⋯ S = 1121231234 \cdots 12345678910111213 \cdots S=112123123412345678910111213你能找出该字符串的第 n n n位数字是多少吗?

输入

一个整数( 1 1 1 < 整数 < 1 0 15 10^{15} 1015),表示所求的位数是多少位

输出

一个整数,表示该位上的数字是多少

解析

其实就是要确定第N位数字所在的项是哪一项。这里分两层。

一层是 L e n ( S n ) Len(S_n) Len(Sn)的长度,一层是 L e n ( ∑ i = 1 n L e n ( S i ) ) Len(\sum_{i=1}^{n} Len(S_i)) Len(i=1nLen(Si))的长度,分别抽象成 f ( x ) , g ( x ) f(x), g(x) f(x),g(x),观察数字长度的特点就可以知道, f ( x ) f(x) f(x)是个特殊的等差数列,而 g ( x ) g(x) g(x)是特殊的等差数列的前 x x x项和。

公差依次是:

1 ∼ 9 10 ∼ 99 100 ∼ 999 ⋯ 1 0 n ∼ 1 0 n + 1 − 1 1 2 3 ⋯ n + 1 \begin{matrix} 1\sim 9 &amp; 10\sim 99 &amp; 100\sim 999 &amp; \cdots &amp; 10^{n} \sim 10^{n+1} -1\\ 1 &amp; 2 &amp; 3 &amp; \cdots &amp; n+1 \end{matrix} 19110992100999310n10n+11n+1

依照这个就可以很容易算出 f ( x ) , g ( x ) f(x), g(x) f(x),g(x),然后利用输入的 n n n二分求上界(可以参考你真的理解二分的写法吗 - 二分写法详解)算出 g ( x ) &lt; n g(x) &lt; n g(x)<n的最大 x x x

利用这个 x x x算出 r e s = n − g ( x ) res = n - g(x) res=ng(x),然后利用输入的 r e s res res二分求上界算出 f ( y ) &lt; r e s f(y) &lt; res f(y)<res的最大 y y y

利用这个 y y y算出 r e s s = r e s − f ( y ) ress = res - f(y) ress=resf(y) r e s s ress ress表示整数 y + 1 y + 1 y+1的第 r e s s ress ress位数字,这就是答案了。

这样做应该是最快的解法了。

代码

#include <bits/stdc++.h>

typedef long long LL;

int main()
{
    auto calFG = [] (LL n) -> std::pair<LL, LL>
    {
        LL start = 0, d = 1, nines = 9, sum = 0, res = 0;
        for (; sum + nines < n; ++d, sum += nines, nines *= 10) {
            res += nines * start + nines * (nines - 1) / 2 * d;
            start += nines * d;
        }
        return { start + d * (n - sum), res + (n - sum + 1) * start + (n - sum + 1) * (n - sum) / 2 * d };
    };
    auto bFind = [] (LL n, const std::function<LL(int)> & func) -> int
    {
        int l = 0, r = 1000000;
        while (l < r) {
            int mid = l + (r - l + 1) / 2;
            if (func(mid) < n) {
                l = mid;
            } else {
                r = mid - 1;
            }
        }
        return l;
    };
    for (LL n; std::cin >> n; ) {
        int ii = bFind(n, std::bind([&] (int m) -> LL
        {
            return calFG(m).second;
        }, std::placeholders::_1));
        LL res = n - calFG(ii).second;
        int i = bFind(res, std::bind([&] (int m) -> LL
        {
            return calFG(m).first;
        }, std::placeholders::_1));
        LL ress = res - calFG(i).first;
        assert(ress);
        std::cout << std::to_string(i + 1).at(ress - 1) << std::endl;
    }
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值