【算法打卡day36(2026-04-02 周四)】DFS专项训练3

- 第 199 篇 -
Date: 2026 - 04 - 01 02 | 周三 周四
Author: 郑龙浩(仟墨)
今日算法:DFS & 记忆化搜索 & 回溯

2026-04-01-02-算法打卡day36-DFS专项训练3

文章目录

13-洛谷P1088-火星人

【题目】

题目描述

人类终于登上了火星的土地并且见到了神秘的火星人。人类和火星人都无法理解对方的语言,但是我们的科学家发明了一种用数字交流的方法。这种交流方法是这样的,首先,火星人把一个非常大的数字告诉人类科学家,科学家破解这个数字的含义后,再把一个很小的数字加到这个大数上面,把结果告诉火星人,作为人类的回答。

火星人用一种非常简单的方式来表示数字――掰手指。火星人只有一只手,但这只手上有成千上万的手指,这些手指排成一列,分别编号为 1 , 2 , 3 , ⋯ 1,2,3,\cdots 1,2,3,⋯。火星人的任意两根手指都能随意交换位置,他们就是通过这方法计数的。

一个火星人用一个人类的手演示了如何用手指计数。如果把五根手指――拇指、食指、中指、无名指和小指分别编号为 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4 和 5 5 5,当它们按正常顺序排列时,形成了 5 5 5 位数 12345 12345 12345,当你交换无名指和小指的位置时,会形成 5 5 5 位数 12354 12354 12354,当你把五个手指的顺序完全颠倒时,会形成 54321 54321 54321,在所有能够形成的 120 120 120 个 5 5 5 位数中, 12345 12345 12345 最小,它表示 1 1 1; 12354 12354 12354 第二小,它表示 2 2 2; 54321 54321 54321 最大,它表示 120 120 120。下表展示了只有 3 3 3 根手指时能够形成的 6 6 6 个 3 3 3 位数和它们代表的数字:

三位数 代表的数字
123 123 123 1 1 1
132 132 132 2 2 2
213 213 213 3 3 3
231 231 231 4 4 4
312 312 312 5 5 5
321 321 321 6 6 6

现在你有幸成为了第一个和火星人交流的地球人。一个火星人会让你看他的手指,科学家会告诉你要加上去的很小的数。你的任务是,把火星人用手指表示的数与科学家告诉你的数相加,并根据相加的结果改变火星人手指的排列顺序。输入数据保证这个结果不会超出火星人手指能表示的范围。

输入格式

共三行。

第一行一个正整数 N N N,表示火星人手指的数目( 1 ≤ N ≤ 10000 1 \le N \le 10000 1≤N≤10000)。

第二行是一个正整数 M M M,表示要加上去的小整数( 1 ≤ M ≤ 100 1 \le M \le 100 1≤M≤100)。

下一行是 1 1 1 到 N N N 这 N N N 个整数的一个排列,用空格隔开,表示火星人手指的排列顺序。

输出格式

N N N 个整数,表示改变后的火星人手指的排列顺序。每两个相邻的数中间用一个空格分开,不能有多余的空格。

输入输出样例 #1
复制代码
5
3
1 2 3 4 5

1 2 4 5 3
说明/提示

对于 30 % 30\% 30% 的数据, N ≤ 15 N \le 15 N≤15。

对于 60 % 60\% 60% 的数据, N ≤ 50 N \le 50 N≤50。

对于 100 % 100\% 100% 的数据, N ≤ 10000 N \le 10000 N≤10000。

noip2004 普及组第 4 题

【思路】

这道题的题目特别长,整理一下题目大意,大概如下:

至于要求什么,我还是读了很久的题的,因为刚开始没明白M的作用,以及输出的到底是什么

  • N表示数组宽度,创建一个nums,存储数组数据
  • 题目中的火星人的数字,实际上就是nums数组中第几个排列
    • 比如12345,路径1是12345,就是火星人的1,
    • 路径2是12354,就是火星人的2,
    • 路径3是12435,就是火星人的3
    • 路径4是12453,就是火星人的4
  • 这里的M,指的就是从1开始,向后数M个
    • 例如题目所说,M是3,就是往后数3个,也就是求路径4是什么,即12453

方法1:我是用DFS去对数组进行排列,然后求出第M+1个路径,输出即可,但是这种写法对于我来说太难了,特别的难写

方法2:AI告诉我,还可以用另一个方法,可以用下面的函数

第一次使用这个函数

cpp 复制代码
#include <algorithm>
bool next_permutation(起始位置, 结束位置);
- 返回 `true`:成功生成了下一个排列
- 返回 `false`:当前已经是最大排列,已将数组变为最小排列

工作原理

假设当前排列是:1 2 3 4 5

调用一次 next_permutation():改变一次数组元素

  • 第1次:1 2 3 5 4
  • 第2次:1 2 4 3 5
  • 第3次:1 2 4 5 3
  • ...直到:5 4 3 2 1
    都返回true

当已经是最大排列时(5 4 3 2 1),再调用会:

  1. 将序列变为最小排列(1 2 3 4 5
  2. 返回 false

所以只需要调用M次next_permutation(arr, arr + N)即可,会自动计算该数组进行M次按照字典序排列后的数组是什么

【代码】

cpp 复制代码
/* 2026-04-01-算法打卡day36-DFS专项训练3
 * 13-洛谷P1088-火星人
 * Author:郑龙浩
 * Date:2026-04-01
 * 方法1:手写DFS(比较难写)
 * 方法2:使用`next_permutation(arr, arr + N)`函数
 */

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int arr[10000 + 10];
int N, M;
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> N >> M;
    for (int i = 0; i < N; i++) cin >> arr[i];
    // 核心算法:执行M次"下一个排列"操作
    // next_permutation(arr, arr + N) 将数组arr[0..N-1]变为字典序中的下一个排列
    // 如果当前已经是最大排列,会变为最小排列(但本题保证结果不会超出范围)
    while (M--) {
        next_permutation(arr, arr + N);
    }
    for (int i = 0; i < N; i++) cout << arr[i] << ' ';
    return 0;
}

【题目】

【思路】

【代码】

cpp 复制代码

【题目】

【思路】

【代码】

cpp 复制代码

14-洛谷B4482-数字游戏II

【题目】

题目描述

千秋正在玩一种数字游戏,这种数字游戏需要在一个 4 × 4 4\times 4 4×4 的网格内填数,每个方格内填入一个 1 ∼ 4 1\sim 4 1∼4 范围内的整数。

游戏胜利当且仅当下面的条件被全部满足:

  • 将网格划分为 4 4 4 个 2 × 2 2\times 2 2×2 的子网格,每个子网格中不存在重复的数
  • 网格的每行不存在重复的数
  • 网格的每列不存在重复的数

子网格的划分如图所示。

千秋已经填好了其中的若干个数,请你完成剩余的数,使得游戏胜利。可能存在多种符合要求的填法,你只需要给出其中一种即可。

输入格式

输入四行,每行四个数,表示网格已经填入的数。

未填入的位置用 0 0 0 表示。

保证已经填入的位置均符合游戏胜利的要求。

输出格式

输出 4 行,每行 4 个整数。表示完成后的游戏局面。

输入输出样例 #1
输入 #1
复制代码
3 0 4 1
4 1 2 0
1 0 3 2
2 3 1 0
输出 #1
复制代码
3 2 4 1
4 1 2 3
1 4 3 2
2 3 1 4
说明/提示

用 n n n 表示未填的格子数目。

对于 5 % 5\% 5% 的测试数据, n = 1 n=1 n=1。

对于另外 25 % 25\% 25% 的测试数据, n = 4 n=4 n=4 且恰好有一行未填。

对于另外 30 % 30\% 30% 的测试数据, n = 7 n=7 n=7 且恰好有一行一列未填。

对于 100 % 100\% 100% 的测试数据, 1 ≤ n ≤ 16 1 \le n \le 16 1≤n≤16,已经填好的数不违反游戏胜利的要求,保证存在符合游戏胜利的解。

【思路】

创建grid数组,并且将数据存入

子块

复制代码
+-------+-------+
| 子块1 | 子块2 |
| A1 B1 | A2 B2 |
| C1 D1 | C2 D2 |
+-------+-------+
| 子块3 | 子块4 |
| A3 B3 | A4 B4 |
| C3 D3 | C4 D4 |
+-------+-------+

坐标:  列0  列1  列2  列3
行0:  (0,0) (0,1) | (0,2) (0,3)
行1:  (1,0) (1,1) | (1,2) (1,3)
      ------------+-------------
行2:  (2,0) (2,1) | (2,2) (2,3)
行3:  (3,0) (3,1) | (3,2) (3,3)

4 个子块分别是

  1. 左上子块:行 0~1,列 0~1
  2. 右上子块:行 0~1,列 2~3
  3. 左下行块:行 2~3,列 0~1
  4. 右下行块:行 2~3,列 2~3

分别给4个子块编号:

  • 左上子块 → 编号 (0,0)
  • 右上子块 → 编号 (0,1)
  • 左下行块 → 编号 (1,0)
  • 右下行块 → 编号 (1,1)

先将数据抽象分为4个部分,方便理解的,不是真的分

这是一个 4×4 数独,但有三个限制:

  1. 每行数字 1~4 不重复
  2. 每列数字 1~4 不重复
  3. 每个 2×2 小方块内数字 1~4 不重复

解决思路

因为棋盘只有 16 个格子,用 DFS & 回溯​ 暴力尝试即可。

步骤

  1. 从左到右、从上到下遍历每个格子

  2. 如果格子已有数字(非0),跳过

  3. 如果是空白(0),尝试填 1~4:

    • 检查填入后是否满足三个条件、限制
      • 需要写一个函数,专门判断填入当前数字后,填入的这个数字是否满足条件
    • 如果满足,填入数字,继续填下一个格子
    • 如果不满足,换下一个数字尝试
  4. 如果所有数字都不行,回溯到上一个格子重新填

  5. 填完最后一个格子时,找到解,结束搜索

【代码】

cpp 复制代码
/* 2026-04-01-02-算法打卡day36-DFS专项训练3
 * 14-洛谷B4482-数字游戏II
 * Author:郑龙浩
 * Date:2026-04-02
 * 算法:
 */

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 4;
int grid[N][N] = {0};
vector <pair <int, int>> coordinate; // 存储空白位置的坐标

bool IS (int x, int y, int num) {
    // 1 判断行是否存在重复数字
    for (int i = 0; i < N; i++) if (num == grid[x][i]) return false;
    // 2 判断列是否存在重复数字
    for (int i = 0; i < N; i++) if (num == grid[i][y]) return false;
    // 3 判断子块内是否存在重复数字
    // 确定是属于哪一个子块
    int startRow, startCol; // 子块的起始坐标

    // 确定x
    if (x == 0 || x == 1) startRow = 0;
    else startRow = 2;
    // 确定y
    if (y == 0 || y == 1) startCol = 0;
    else startCol = 2;

    // 查找子块内是否重复
    for (int i = startRow; i < startRow + 2; i++)
        for (int j = startCol; j < startCol + 2; j++) {
            if (grid[i][j] == num) return false;
        }
    return true;
}

// pos是存储空白位置坐标的索引值
// pos是还没有添加数值或者正在添加数值的位置
bool dfs(int pos) {
    if (pos == coordinate.size()) { // 如果pos到了cnt,就说明0~cnt-1所有的空白位置都已经填入了,此时直接renturn即可,已经填充完毕
        return true;
    }
    
    int curX = coordinate[pos].first;
    int curY = coordinate[pos].second;
    for (int num = 1; num <= 4; num++) {
        if (IS(curX, curY, num)) {
            grid[curX][curY] = num;
            if (dfs(pos + 1)) return true;
            grid[curX][curY] = 0; // 回溯
        }
    }
    return false;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) {
        cin >> grid[i][j];
        if (grid[i][j] == 0) {
            coordinate.push_back({i, j});
        }
    }
    dfs(0);
    for (int i = 0; i < N; i ++) {
        // 输出一定要注意:最后一个字符后面不能是' ',所以要让其后面单独输出空格
        for (int j = 0; j < N - 1; j++) cout << grid[i][j] << ' ';
        cout << grid[i][N - 1] << '\n';
    }
    return 0;
}

15-洛谷B3862-图的遍历简单版-DFSBFS

【题目】

题目描述

给出 N N N 个点, M M M 条边的有向图,对于每个点 v v v,求 A ( v ) A(v) A(v) 表示从点 v v v 出发,能到达的编号最大的点。

输入格式

第 1 1 1 行 2 2 2 个整数 N , M N,M N,M,表示点数和边数。

接下来 M M M 行,每行 2 2 2 个整数 U i , V i U_i,V_i Ui,Vi,表示边 ( U i , V i ) (U_i,V_i) (Ui,Vi)。点用 1 , 2 , ... , N 1,2,\dots,N 1,2,...,N 编号。

输出格式

一行 N N N 个整数 A ( 1 ) , A ( 2 ) , ... , A ( N ) A(1),A(2),\dots,A(N) A(1),A(2),...,A(N)。

输入输出样例 #1

输入1

复制代码
4 3
1 2
2 4
4 3

输出

复制代码
4 4 3 4
说明/提示
  • 对于 100 % 100\% 100% 的数据, 1 ≤ N , M ≤ 10 3 1 \leq N,M \leq 10^3 1≤N,M≤103。

【思路】

【代码】

cpp 复制代码
/* 2026-04-01-02-算法打卡day36-DFS专项训练3
 * 15-洛谷B3862-图的遍历简单版-DFSBFS
 * Author:郑龙浩
 * Date:2026-04-02
 * 算法:DFS
 * 思路:本来求的是点v能到达的编号最大的点,但是反过来想,从最大的点A(v)往小走,A(v)不就是这些途径的小点的最大点了吗
 */

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int N, M;
// DFS遍历反向图,标记所有能到达当前最大点的节点
// 反向图(v->u表示原图中u能到达v)
// maxPoints 存储每个点能到达的最大编号
// pos 当前遍历的节点
// maxPoint 当前正在遍历的起点编号(即pos的最大可达节点)
void dfs(vector <vector <int>>& graph, bool visited[], int maxPoints[], int pos, int maxPoint) {
    visited[pos] = true;
    maxPoints[pos] = maxPoint; // 这个点在原图中能到达maxPoint,且maxPoint就是pos最大编号
    for (int next = 0; next < graph[pos].size(); next++) {
        int nextPoint = graph[pos][next];
        if (!visited[nextPoint]) // 如果当前节点没有被访问过,就可以访问
            dfs(graph, visited, maxPoints, nextPoint, maxPoint);
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> N >> M;
    vector <vector <int>> graph (N + 1); // 编号从1开始的,所以多开一个
    int u, v;
    int M2 = M;
    while (M2--) {
        cin >> u >> v;
        graph[v].push_back(u); // 反向存图,u-> v,变成v->u
    }
    int maxPoints[N + 1] = {0}; // 存储每个节点的最大点    
    bool visited[N + 1] = {false}; // 访问过的
    // 从编号大的反向寻找
    for (int i = N; i >= 1; i--) {
        if (visited[i] == false) 
            dfs(graph, visited, maxPoints, i, i); // 遍历所有比N小的点,并且将他们的大点赋值为i
    }
    for (int i = 1; i <= N; i++)
        cout << maxPoints[i] << ' ';
    return 0;
}

【题目】洛谷P4961-小埋与扫雷

题目背景

小埋总是在家中打游戏,一天,她突然想玩Windows自带的扫雷,在一旁的哥哥看见了,想起了自己小时候信息课在机房玩扫雷的日子,便兴致勃勃地开始教小埋扫雷。然而,小埋还是不明白 3 b v \mathrm{3bv} 3bv(Bechtel's Board Benchmark Value,每局将所有非雷的方块点开所需最少左键点击数,参见扫雷网的教程 )怎么算,于是她找到了你。

![](https://i.loli.net/2018/10/04/5bb5bd6aefb70.jpg

题目描述

小埋会告诉你一盘扫雷,用一个 n × m n\times m n×m 的矩阵表示, 1 1 1 是雷 , 0 0 0 不是雷,请你告诉她这盘扫雷的 3 b v \mathrm{3bv} 3bv 。

周围八格没有"雷"且自身不是"雷"的方格称为"空格",周围八格有"雷"且自身不是"雷"的方格称为"数字",由"空格"组成的八连通块称为一个"空"。3bv= 周围八格没有"空格"的"数字"个数+"空"的个数。

如果看不懂上面的计算方式,可以看题目背景中给出的教程,或者看下面的样例解释。

输入格式

第一行有两个整数 n n n 和 m m m,代表这盘扫雷是一个 n × m n \times m n×m 的矩阵。

后面的 n n n 行每行有 m m m 个整数,表示这个矩阵,每个数字为 0 0 0 或 1 1 1, 1 1 1 代表是雷, 0 0 0 代表不是雷。

输出格式

一个整数,代表这盘扫雷的 3 b v \mathrm{3bv} 3bv 。

输入输出样例 #1
复制代码
8 8
0 0 0 1 1 0 0 0 
1 0 0 1 0 0 0 1 
1 0 0 1 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 1 0 0 0 0 0 0 

13
说明/提示

1 ≤ n , m ≤ 1000 1\le n,\ m\le 1000 1≤n, m≤1000

样例解释

【思路】

整理一下:

八连通(8个方向)

空格:八连通无雷

数字:八连通雷的数量

空:说白了就是,连起来的「空」,就和「孤岛」那一系列题中的岛屿或者海洋一样,类似于求海洋的个数或者岛屿的个数

个人感觉主要难点在于对「空」的理解,我读题的时候,理解了好久的「空」到底是个什么含义

求:求「空」的个数 + 周围没有「空」且当前位置是「数字」的个数

【代码】

cpp 复制代码
/* 2026-04-01-02-算法打卡day36-DFS专项训练3
 * 16-洛谷P4961-小埋与扫雷
 * Author:郑龙浩
 * Date:2026-04-02
 * 算法:DFS
 */

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000 + 10;
int n, m;
int grid[N][N] = {0};
bool visited[N][N] = {false};
int Grid[N][N] = {0};
int direction[8][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}, {1, -1}, {-1, 1}, {-1, -1}, {1, 1}};
// 将所有与传入的xy相连的空白标记为true,也就是,标记为访问过
void dfs(int x, int y) {
    visited[x][y] = true;
    int nextX, nextY;
    for (int i = 0; i < 8; i++) {
        nextX = x + direction[i][0];
        nextY = y + direction[i][1];
        if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m) continue;
        if (Grid[nextX][nextY] == 0 && visited[nextX][nextY] == false)
            dfs(nextX, nextY);
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    cin >> n >> m;
    for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) cin >> grid[i][j];
    // 1 计算雷数
    for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
        if (grid[i][j] == 1) {Grid[i][j] = -1; continue;} // 跳过雷,雷设为-1
        // 计算无雷位置周围有多少个雷
        int nextX, nextY;
        for (int k = 0; k < 8; k++) {
            nextX = i + direction[k][0];
            nextY = j + direction[k][1];
            if (nextX < 0 || nextX >= n || nextY < 0|| nextY >= m) continue;
            Grid[i][j] += grid[nextX][nextY];
        }
    }
    // for (int i = 0; i < n; i++) {
    //     for (int j = 0; j < m; j++) cout << Grid[i][j] << ' ';
    //     cout << '\n';
    // }
    int cnt = 0;
    // 2 计算「周围8个没有空格(是雷和数字) 且 当前位置是数字」的个数
    for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
        int nextX, nextY;
        if (Grid[i][j] == 0 || Grid[i][j] == -1) continue; // 必须保证当前位置是个数字,如果不是数字,则直接跳过当前位置的判断
        bool f = true; // 假设当前位置是符合条件的
        for (int k = 0; k < 8; k++) {
            nextX = i + direction[k][0];
            nextY = j + direction[k][1];
            // 越界不妨问
            if (nextX < 0 || nextX >= n || nextY < 0 || nextY >= m) continue;
            // 如果周围位置有任何一个空格,不可能再符合条件
            if (Grid[nextX][nextY] == 0) {f = false; break;}
        }
        if (f) cnt++; // 如果是符合「周围8个没有空格的数字」这个条件的数字,就计数
    }
    // 3 计算有多少个「空」,也就是连起来的「空格」算1个空,计算数量
    for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {
        if (Grid[i][j] == 0 && visited[i][j] == false) {
            dfs(i, j);
            cnt++;
        }
    }
    cout << cnt;
    return 0;
}

17-洛谷P1149-火柴棒等式

【题目】

题目描述

给你 n n n 根火柴棍,你可以拼出多少个形如 A + B = C A+B=C A+B=C 的等式?等式中的 A A A、 B B B、 C C C 是用火柴棍拼出的整数(若该数非零,则最高位不能是 0 0 0)。用火柴棍拼数字 0 ∼ 9 0\sim9 0∼9 的拼法如图所示:

注意:

  1. 加号与等号各自需要两根火柴棍;
  2. 如果 A ≠ B A\neq B A=B,则 A + B = C A+B=C A+B=C 与 B + A = C B+A=C B+A=C 视为不同的等式( A , B , C ≥ 0 A,B,C\geq0 A,B,C≥0);
  3. n n n 根火柴棍必须全部用上。
输入格式

一个整数 n ( 1 ≤ n ≤ 24 ) n(1 \leq n\leq 24) n(1≤n≤24)。

输出格式

一个整数,能拼成的不同等式的数目。

输入输出样例 #1
输入 #1
复制代码
14
输出 #1
复制代码
2
输入输出样例 #2
输入 #2
复制代码
18
输出 #2
复制代码
9
说明/提示

【输入输出样例 1 解释】

2 2 2 个等式为 0 + 1 = 1 0+1=1 0+1=1 和 1 + 0 = 1 1+0=1 1+0=1。

【输入输出样例 2 解释】

9 9 9 个等式为

0 + 4 = 4 0+4=4 0+4=4、 0 + 11 = 11 0+11=11 0+11=11、 1 + 10 = 11 1+10=11 1+10=11、 2 + 2 = 4 2+2=4 2+2=4、 2 + 7 = 9 2+7=9 2+7=9、 4 + 0 = 4 4+0=4 4+0=4、 7 + 2 = 9 7+2=9 7+2=9、 10 + 1 = 11 10+1=11 10+1=11、 11 + 0 = 11 11+0=11 11+0=11。

noip2008 提高第二题

【思路】

捋一下思路:

  • 首先

    数字:
    0 1 2 3 4 5 6 7 8 9
    分别对应火柴数量:
    6 2 5 5 4 5 6 3 7 6

  • 那么我就可以根据火柴数量去找对应的数字了:

    • 刚开始我想的挺简单的,我以为一个火柴数量对应一个数字,直接写一个map映射就好了
    • 然后我数了一下,反向同等数量的火柴可以拼出不同的数字,
    • 那这个映射我就要改一下了:一个火柴数量可以映射出1个和多个数字 ,如下:
      6个火柴可以拼出3个数字,2拼1个,5拼3个,3拼1个,7拼1个
  • 2根火柴组成的:1

  • 4根火柴组成的:4

  • 5根火柴组成的:2 3 5

  • 6根火柴组成的:0 6 9

  • 7根火柴组成的:8

题目条件

  1. 加号与等号各自需要两根火柴棍;
  2. 如果 A=B,则 A+B=C 与 B+A=C 视为不同的等式(A,B,C≥0);
  3. n 根火柴棍必须全部用上。

那么输入n之后,我直接减去4,也就是实际消耗是n-4,如果为负数,可以直接输出0了

整理了一下,大题思路就是,根据火柴数量,去遍历所有可能组成的数字:

写一个二维数组,一维索引分别对应2根火柴,4根火柴,...7根火柴
二维代表对应的能组成数字,宽度根据能组成的数字而定

思路错了,不应该根据火柴去拼数字,应该反过来想根据数字,去拼火柴

重新捋一下:

n的范围是1~24,去掉+和=的火柴数,也就是最多20个

然后需要拼3个数字,那么单个数字最多4位数,也就是我只需要尝试所有的四位数就好了,假设就是最大2000,随便设置的,如果还不行,那就再加数字测试

假设我的数字范围是1~2000,那么就不断尝试就好了

  • 首先,计算出1~2000所有数字需要用到的火柴数
  • 然后,不断尝试n-4,需要用到的火柴数量的所有可能,如果找到一个就cnt++,最后输出即可

绝对不可能组成数字的情况:

  • 加号和等号共需4个火柴
  • a + b = c,最少的情况,假设每个位置都是1,再给加个3吧
  • 就按照小于等于 4 + 3 ,7个火柴的时候,直接输出0
  • 当然不准确,具体最小的情况懒得用人脑去想了,直接交给计算机好了,数据量也不大

【代码】

cpp 复制代码
/* 2026-04-01-02-算法打卡day36-DFS专项训练3
 * 17-洛谷P1149-火柴棒等式
 * Author:郑龙浩
 * Date:2026-04-02
 */

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int numCnt[10] = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
// 返回数字num需要的火彩数量
int Num(int num) {
    if (num == 0) return 6; // 刚开始忘记写这个了
    int cnt = 0;
    while (num) {
        cnt += numCnt[num % 10];
        num /= 10;
    }
    return cnt;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n; cin >> n;
    if (n <= 7) {cout << 0; return 0;}

    int numCnt2[2001] = {0};
    // 计算出1 ~ 2000所需的火柴数量
    for (int i = 0; i <= 2000; i++) {
        numCnt2[i] = Num(i);
    }
    n -= 4;
    int cnt = 0;
    for (int i = 0; i <= 2000; i++) {
        if (numCnt2[i] >= n) continue; // 如果火柴数量超了,就尝试下一组的数据
        for (int j = 0; j <= 2000; j++) {
            if (numCnt2[i] + numCnt2[j] >= n) continue; // 如果火柴数量超了,就尝试下一组
            int c = i + j;
            if (c > 2000) continue; // 如果c大于我设置的限制,也要尝试下一组,因为c如果大于2000,我是没有计算其火柴数量的
            if (numCnt2[i] + numCnt2[j] + numCnt2[c] == n) cnt++;
        }
    }
    cout << cnt;
    return 0;
}
相关推荐
B1acktion2 小时前
2.3.插入排序——像打牌一样整理数组,为什么它对“几乎有序”数据特别友好?
数据结构·算法·排序算法
Mr_Xuhhh2 小时前
C++算法刷题:排序子序列、削减整数、最长上升子序列(二)题解
开发语言·c++·算法
tankeven2 小时前
HJ157 剪纸游戏
c++·算法
迈巴赫车主2 小时前
蓝桥杯 19717 挖矿java
java·开发语言·数据结构·算法·职场和发展·蓝桥杯
airuike1232 小时前
高性能MEMS IMU:机器人自主运动的核心感知中枢
人工智能·算法·机器人
郝学胜-神的一滴2 小时前
PyTorch张量维度操控:transpose与permute深度拆解与实战指南
人工智能·pytorch·python·深度学习·算法·机器学习
未来之窗软件服务2 小时前
SenseVoicecpp ggml-cann.cpp大模型[AI人工智能(七十六)]—东方仙盟
人工智能·算法·sensevoice·仙盟创梦ide·东方仙盟
Trouvaille ~3 小时前
零基础入门 LangChain 与 LangGraph(一):理解大模型、提示词、Embedding 和接入方式
算法·langchain·大模型·embedding·rag·langgraph·llm应用
xiaoye-duck3 小时前
《算法题讲解指南:动态规划算法--简单多状态dp问题》--17.买卖股票的最佳时机III,18.买卖股票的最佳时机IV
c++·算法·动态规划