题解:洛谷 P1211 牛式

【题目来源】

洛谷:P1211 [USACO1.3] 牛式 Prime Cryptarithm - 洛谷

【题目描述】

下面是一个乘法竖式,如果用我们给定的那 n 个数字来取代 *,可以使式子成立的话,我们就叫这个式子为牛式。

          ***
    x      **
   ----------
         ***
        ***
   ----------
        ****

数字只能取代 *,当然第一位不能为 0,况且给定的数字里不包括 0。

注意一下在美国的学校中教的“部分乘积”,第一部分乘积是第二个数的个位和第一个数的积,第二部分乘积是第二个数的十位和第一个数的乘积。

请计算出牛式的数量。

【输入】

第一行一个正整数 n,表示可用的数集。第二行 n 个正整数 ai,表示可用的数。

【输出】

输出一行一个整数,表示牛式的总数。

【输入样例】

5
2 3 4 6 8

【输出样例】

1

【核心思想】

  1. 问题分析:给定 \(n\) 个可用数字(不含 0),要求找出满足特定乘法竖式的"牛式"数量。竖式形式为一个三位数乘以一个两位数,得到两个部分积(均为三位数)和一个最终积(四位数),且所有出现的数字都必须在给定的数集中。这是一个枚举 + 数位验证问题。

  2. 算法选择

    • 暴力枚举:枚举所有可能的三位数 \(i \in [111, 999]\) 和两位数 \(j \in [11, 99]\)
    • 数位拆分验证:将数字逐位拆分,检查每一位是否都在给定的可用数字集合中
    • 桶标记快速判断:用布尔数组 \(a[d]\) 标记数字 \(d\) 是否可用,实现 \(O(1)\) 数位查询
  3. 关键步骤

    • 读入与标记:读入 \(n\) 个可用数字,用桶数组 \(a[d] = 1\) 标记
    • 定义验证函数 check(x, k)
      • \(x \geq 10^k\) 返回 false(保证位数恰好为 \(k\) 位)
      • 逐位拆分 \(x\),若某位数字 \(d\) 满足 \(a[d] = 0\) 则返回 false
      • 全部通过返回 true
    • 双重枚举
      • 外层 \(i\)\(111\)\(999\)(被乘数,三位数)
      • 内层 \(j\)\(11\)\(99\)(乘数,两位数)
      • 验证五个条件:
        • check(i, 3):被乘数是三位数且各位可用
        • check(j, 2):乘数是两位数且各位可用
        • check(i * (j % 10), 3):第一部分积(个位相乘)是三位数且各位可用
        • check(i * (j / 10), 3):第二部分积(十位相乘)是三位数且各位可用
        • check(i * j, 4):最终积是四位数且各位可用
      • 全部满足则 ans++
    • 输出结果:牛式总数 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(900 \times 90 \times \log_{10}(\text{max})) \approx O(10^5)\),枚举量小,数位验证为常数级
    • 空间复杂度:\(O(1)\),仅需桶数组和少量变量
  5. 枚举验证的核心思想

    • 竖式约束转化:将乘法竖式的位数要求转化为五个独立的数位验证条件
    • 桶数组加速:用 \(a[d]\) 标记可用数字,避免每次线性查找,实现 \(O(1)\) 判断
    • 位数精确控制check(x, k) 中先判断 \(x < 10^k\) 确保不超出 \(k\) 位,再通过循环拆分确保不短于 \(k\) 位(因不含 0,不会出现前导零问题)
    • 范围剪枝\(i\)\(111\) 开始而非 \(100\)\(j\)\(11\) 开始而非 \(10\),因可用数字不含 0,自然排除含 0 的数
    • 适用于数字谜题、竖式还原、数位约束枚举等问题

【解题思路】

【算法标签】

普及- #模拟

【代码详解】

#include <bits/stdc++.h>
using namespace std;
int n, t, ans=0;
int a[15];
bool check (int x, int k)  // 定义判断数位的函数
{
    if (x>=pow(10, k)) return false;  // 如果这个数x大于等于10^k,则返回false
    while (x>0) {  // 使用数位拆分
        if (a[x%10] == 0) return false;  // 依次判断每个数都在n个数字中,如果不在的话直接返回false
        x = x / 10;  // 数位拆分
    }
    return true;  // 如果没有不满足的话,那就返回true
}
int main()
{
    cin >> n;  // 输入n
    for (int i=1; i<=n; i++) {  // 遍历n个数
        cin >> t;  // 输入每个数
        a[t]=1;  // 并使用桶排序方法存到桶中,后面需要判断该数字是否有
    }
    for (int i=111; i<=999; i++) {  // 遍历所有的三位数
        for (int j=11; j<=99; j++) {  // 遍历所有的二位数
            if (check(i, 3) && check(j, 2) && check(i * (j%10), 3) && check(i * (j/10), 3) && check (i*j, 4)) {  // 根据题目要求,判断两个数是否为一个为3位数,一个为2位数,3位数乘上2个位的个位和十位都是3位数,以及3位数乘2位数为4位数
                ans++;  // 统计结果自增1(只有222*22满足此要求)
            }
        }
    }
    cout << ans << endl;
    return 0;
}

【运行结果】

5
2 3 4 6 8
1
posted @ 2026-08-03 08:46  团爸讲算法  阅读(14)  评论(0)    收藏  举报