当前位置:首页 > 技术 > 正文内容

最少1的个数:将整数表示为纯1数的和

访客 技术 2026年6月8日 1

问题概述

给定一个正整数 \(n\)(\(1 \le n < 10^{15}\)),要求将其拆分为若干个数之和,每个数只能是仅由数字1构成的整数(可正可负)。目标是使所有加数中包含的数字1的总个数最少。

例如,\(121 = 111 + 11 + (-1)\),使用了 \(3+2+1 = 6\) 个数字1。

思路分析

记 \(y_k\) 为 \(k\) 个1组成的数,即 \(y_k = \underbrace{111\ldots 1}_{k\text{个}}\)。观察发现,一个 \(y_{k+1}\) 可以用 \(y_k\) 和 \(y_1\) 表示:

\[ y_{k+1} = 10y_k - y_1 \]

首先,将 \(n\) 拆解为各个数位的和,每个数位对应一组 \(y_i\) 的系数。以 \(n = 150\) 为例,其十进制分解为 \(1 \times 10^2 + 5 \times 10^1 + 0 \times 10^0\),转换后得到:

  • \(1\) 个 \(y_3\)(对应 \(10^2\))
  • \(4\) 个 \(y_2\)(因为 \(5 \times 10^1 = 5 \times (10y_1 - y_1)\),化简后得到)
  • \(-5\) 个 \(y_1\)

设 \(a_i\) 为 \(y_{i+1}\) 的系数(下标从0开始,即 \(a_0\) 对应 \(y_1\),\(a_1\) 对应 \(y_2\),依此类推)。通过高位变换,可以调整这些系数以减少绝对值和。高位借位规则如下:

  • 若 \(a_x > 0\):可从 \(y_{x+2}\) 借1,使得 \(a_x\) 减少10,\(a_0\) 减少1,\(a_{x+1}\) 增加1。
  • 若 \(a_x < 0\):可向 \(y_{x+2}\) 还1,使得 \(a_x\) 增加10,\(a_0\) 增加1,\(a_{x+1}\) 减少1。

这个方法利用了 \(y_{k+1} = 10y_k - y_1\) 的关系。由于数字长度最多为16(\(10^{15}\) 有16位),因此最多有17种 \(y_i\)。采用深度优先搜索(DFS)遍历每个位置是否借位,最终统计 \(\sum |a_i| \times (i+1)\) 的最小值。

代码实现

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;

typedef long long LL;
const int N = 110;

LL n;
vector<int> digits;
int coeff[N], len, best = 0x3f3f3f3f;

void dfs(int pos) {
    if (pos > len) {
        int total = 0;
        for (int i = 0; i <= len; ++i)
            total += abs(coeff[i]) * (i + 1);
        best = min(best, total);
        return;
    }
    // 不进行任何借位操作
    dfs(pos + 1);
    
    if (coeff[pos] > 0) {
        // 借高位:coeff[pos] -= 10, coeff[0] -= 1, coeff[pos+1] += 1
        coeff[pos + 1] += 1;
        coeff[pos] -= 10;
        coeff[0] -= 1;
        dfs(pos + 1);
        // 回溯
        coeff[0] += 1;
        coeff[pos] += 10;
        coeff[pos + 1] -= 1;
    } else if (coeff[pos] < 0) {
        // 还高位:coeff[pos] += 10, coeff[0] += 1, coeff[pos+1] -= 1
        coeff[pos + 1] -= 1;
        coeff[pos] += 10;
        coeff[0] += 1;
        dfs(pos + 1);
        // 回溯
        coeff[0] -= 1;
        coeff[pos] -= 10;
        coeff[pos + 1] += 1;
    }
}

int main() {
    cin >> n;
    while (n) {
        digits.push_back(n % 10);
        n /= 10;
    }
    len = digits.size();
    // 初始化系数:从高位到低位处理
    for (int i = len - 1; i >= 0; --i) {
        coeff[i] += digits[i];
        if (i) coeff[i - 1] -= digits[i];
    }
    dfs(0);
    cout << best << endl;
    return 0;
}

另一种解法:直接搜索并用预计算

另一种思路是预先生成 \(y_k\) 的数值(直到 \(y_{16}\)),然后对 \(n\) 进行深度优先搜索,每次选择使用还是不使用当前最高位的 \(y_k\),并尝试用 \(y_k\) 抵消一部分数字,递归到低位。

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

typedef long long LL;

// 预计算 y_k = 111...1 (k个1)
LL ones[20] = {0, 1, 11, 111, 1111, 11111, 111111, 1111111, 11111111, 111111111,
               1111111111, 11111111111, 111111111111, 1111111111111,
               11111111111111, 111111111111111, 1111111111111111, 11111111111111111};
LL n, ans = 1e18;

int find_highest(LL x) {
    int l = 1, r = 16;
    while (l < r) {
        int mid = (l + r) >> 1;
        if (x <= ones[mid]) r = mid;
        else l = mid + 1;
    }
    return l;
}

void dfs(int level, LL remaining, LL count) {
    if (level == 0) {
        if (remaining == 0) ans = min(ans, count);
        return;
    }
    int k = find_highest(remaining);
    // 选择1:使用 y_k 并可能再借一个 y_k
    LL borrow = ones[k] - remaining;
    dfs(level - 1, borrow % ones[level], count + borrow / ones[level] * level + k);
    // 选择2:不使用 y_k,直接取模
    dfs(level - 1, remaining % ones[level], count + remaining / ones[level] * level);
}

int main() {
    cin >> n;
    dfs(16, n, 0);
    cout << ans << endl;
    return 0;
}

测试示例

  • 输入:121 → 输出:6
  • 输入:150 → 输出:15
  • 输入:19 → 输出:7
  • 输入:111 → 输出:3

总结

两种方法都利用了"借位"思想,将大数分解转化为对系数或数字的调整,最终通过搜索找到最小1的个数。时间复杂度在可接受范围内。

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。