题解:洛谷 P1211 牛式
【题目来源】
洛谷:P1211 [USACO1.3] 牛式 Prime Cryptarithm - 洛谷
【题目描述】
下面是一个乘法竖式,如果用我们给定的那 n 个数字来取代 *,可以使式子成立的话,我们就叫这个式子为牛式。
***
x **
----------
***
***
----------
****
数字只能取代 *,当然第一位不能为 0,况且给定的数字里不包括 0。
注意一下在美国的学校中教的“部分乘积”,第一部分乘积是第二个数的个位和第一个数的积,第二部分乘积是第二个数的十位和第一个数的乘积。
请计算出牛式的数量。
【输入】
第一行一个正整数 n,表示可用的数集。第二行 n 个正整数 ai,表示可用的数。
【输出】
输出一行一个整数,表示牛式的总数。
【输入样例】
5
2 3 4 6 8
【输出样例】
1
【核心思想】
-
问题分析:给定 \(n\) 个可用数字(不含 0),要求找出满足特定乘法竖式的"牛式"数量。竖式形式为一个三位数乘以一个两位数,得到两个部分积(均为三位数)和一个最终积(四位数),且所有出现的数字都必须在给定的数集中。这是一个枚举 + 数位验证问题。
-
算法选择:
- 暴力枚举:枚举所有可能的三位数 \(i \in [111, 999]\) 和两位数 \(j \in [11, 99]\)
- 数位拆分验证:将数字逐位拆分,检查每一位是否都在给定的可用数字集合中
- 桶标记快速判断:用布尔数组 \(a[d]\) 标记数字 \(d\) 是否可用,实现 \(O(1)\) 数位查询
-
关键步骤:
- 读入与标记:读入 \(n\) 个可用数字,用桶数组 \(a[d] = 1\) 标记
- 定义验证函数
check(x, k):- 若 \(x \geq 10^k\) 返回
false(保证位数恰好为 \(k\) 位) - 逐位拆分 \(x\),若某位数字 \(d\) 满足 \(a[d] = 0\) 则返回
false - 全部通过返回
true
- 若 \(x \geq 10^k\) 返回
- 双重枚举:
- 外层 \(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\)
-
时间/空间复杂度:
- 时间复杂度:\(O(900 \times 90 \times \log_{10}(\text{max})) \approx O(10^5)\),枚举量小,数位验证为常数级
- 空间复杂度:\(O(1)\),仅需桶数组和少量变量
-
枚举验证的核心思想:
- 竖式约束转化:将乘法竖式的位数要求转化为五个独立的数位验证条件
- 桶数组加速:用 \(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
浙公网安备 33010602011771号