- 第 200 篇 -
Date: 2026 - 04 - 04 | 周六
Author: 郑龙浩(仟墨)
今日算法:DFS & 枚举 & 蓝桥杯真题
文章目录
- 2026-04-04-算法打卡day37-DFS专项训练4-枚举专项训练1-全部是蓝桥杯真题
- 2026-04-01-04-算法打卡day37-DFS专项训练4-全部是蓝桥杯真题
- 2026-04-01-04-算法打卡day37-枚举专项训练1-全部是蓝桥杯真题
2026-04-04-算法打卡day37-DFS专项训练4-枚举专项训练1-全部是蓝桥杯真题
2026-04-01-04-算法打卡day37-DFS专项训练4-全部是蓝桥杯真题
【题目】1-洛谷P12139-黑白棋-DFS
题目描述
小蓝最近迷上了一款名为"黑白棋填充"的游戏。该游戏在一个方形网格棋盘上进行,其中部分格子已经填有黑色或白色的棋子,而其他格子为空,等待玩家填入棋子。
游戏规则是,玩家需要按照以下规则填满整个棋盘,才能算作胜利:
- 黑白棋子数量均等 :
在每一行和每一列中,黑色棋子和白色棋子的数量必须相等。 - 相邻棋子限制 :
在棋盘的任何一行或一列中,不能有超过两个相同颜色的棋子连续排列(即不允许出现"黑黑黑"或"白白白"的情况)。 - 行列唯一性 :
每一行的棋子排列方式必须是唯一的,不能与棋盘中的任何其他行完全相同。
每一列的棋子排列方式必须是唯一的,不能与棋盘中的任何其他列完全相同。
行与列之间的棋子排列不作比较,即行可以与列相同,无需满足行列间的唯一性。

现在有一个 6 × 6 6 \times 6 6×6 的棋盘,如上图所示,其中部分格子已填入棋子(黑色或白色),其余格子需要你填充,题目保证有唯一解。
请给出唯一的正确解,并按照以下格式输出答案:
- 黑色棋子用 1 1 1 表示,白色棋子用 0 0 0 表示。
- 从左到右、从上到下的顺序,依次遍历棋盘上的所有格子,并将这些值拼接成一个长度为 36 36 36 的字符串。
例如,假设最终填充完成后的棋盘如下(仅为示例,并非真实答案):
plain
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 1 1 0 0
0 0 1 1 1 1
则输出结果应为:100000000000000000001000001100001111。
输出格式
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只需要编写一个程序输出这个整数,输出多余的内容将无法得分。
【思路】
这道题用了我好长时间,刚开始大体思路和写法是对的
但是我直接将条件1和条件3的判断写在了dfs填充过程中,也就是填1个判断3个条件同时判断
完全错了,因为填充过程中还有很多位置压根没有填充棋子,也就是意味着,我的判断是无效且错误的
必须在全部填充后才能进进行判断,然后我就将条件2单独摘出来,条件2在过程中判断可以提前「剪枝」
当然不再过程中判断,在最后判断也是可以的,毕竟数据量特别小,无所谓多递归的这些层
然后条件1和条件3写在了全部填充后再判断
【代码】
cpp
/* 2026-04-01-03-算法打卡day37-DFS专项训练4-全部是蓝桥杯真题
* 1-洛谷P12139-黑白棋 [蓝桥杯 2025 省 A]
* Author:郑龙浩
* Date:2026-04-03
* 算法:
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 6;
int grid[N][N] = {
1, 0, -1, 0, -1, -1,
-1, -1, -1, 0, -1, -1,
-1, -1, -1, -1, 0, 0,
-1, -1, -1, -1, -1, -1,
-1, -1, 1, -1, -1, 1,
-1, 0, -1, -1, 1, -1
};
int rowCntB[N] = {0}; // 每一行黑棋数量
int rowCntW[N] = {0}; // 每一行白棋数量
int colCntB[N] = {0}; // 每一列黑棋数量
int colCntW[N] = {0}; // 每一列白棋数量
// 是空白的坐标
int coordinateX[N * N] = {0};
int coordinateY[N * N] = {0};
int len = 0; // 空白坐标的数量
bool IS1(int x, int y) {
// 2 判断是否同一行或列中有相同的颜色的大于两个的棋子连续排列
// 检查连续棋子
// 检查行
for (int j = 0; j < N - 2; j++) {
if (grid[x][j] != -1 && grid[x][j] == grid[x][j+1] && grid[x][j] == grid[x][j+2])
return false;
}
// 检查列
for (int j = 0; j < N - 2; j++) {
if (grid[j][y] != -1 && grid[j][y] == grid[j+1][y] && grid[j][y] == grid[j+2][y])
return false;
}
return true; // 如果没有违反任何一个条件,直接返回true
}
// 单独判断条件3,刚开始我把条件3写到了IS与前面两个同时判断,这样的话,可能行列中还有很多位置-1,也就是没有填入的状态,此时就一定不符合条件,所以应该在全部填入后再进行第三个条件的判断
// 所以正确的检查策略应该是:过程中检查条件2,看是否有>2个的连续相同棋子,条件1和条件3放到最后才进行检查
bool IS2() {
// 1 如果行或者列的白与黑不相同,直接return false即可
for (int i = 0; i < N; i++)
if (rowCntB[i] != rowCntW[i] || colCntB[i] != colCntW[i]) return false;
// 3 判断行列是否有相同的排列
for (int i = 0; i < N; i++) {
for (int j = i + 1; j < N; j++) {
bool rowf = true; // 假设j行与i行是相等的
for (int col = 0; col < N; col++) {
if (grid[i][col] != grid[j][col]) {rowf = false; break;}
}
if (rowf == true) return false;
bool colf = true; // 假设i列与j列是相等的
for (int row = 0; row < N; row++) {
if (grid[row][i] != grid[row][j]) {colf = false; break;}
}
if (colf == true) return false; // 如果有任一行和列相同,直接return false
}
}
return true; // 如果全都满足,会走到这一步的,直接返回true
}
bool dfs(int index) { // index指的是正在添加index位置的棋子(刚传参的时候还未添加,整个dfs执行到后面才算添加)
if (index == len) return IS2(); // 如果index到达了len就说明 [0, len - 1] 之间的所有空白位置都填上了,再判断是否符合条件13,如果符合返回true,否则false
// 取出需要填充棋子的坐标
int curX = coordinateX[index];
int curY = coordinateY[index];
// 直接写一个for,只遍历0 ~ 1,白棋是0,黑棋是1
for (int color = 0; color < 2; color++) {
grid[curX][curY] = color; // 填充棋子
if (color == 1) {
rowCntB[curX]++; // 行的黑色棋子数量++
colCntB[curY]++; // 列的黑色棋子数量++
} else {
rowCntW[curX]++; // 行的白色棋子数量++
colCntW[curY]++; // 列的白色棋子数量++
}
if (IS1(curX, curY)) { // 如果符合条件2,连续颜色
if (dfs(index + 1)) return true;
}
// 如果没有找到一个合适的填充方法的话,dfs会返回false,此时进行回溯
grid[curX][curY] = -1; // 重新将该位置变为空白棋盘方格
if (color == 1) {
// 回溯,将刚才计入的黑色棋子数量再次--
rowCntB[curX]--; // 行的黑色棋子数量--
colCntB[curY]--; // 列的黑色棋子数量--
} else {
// 回溯,将刚才计入的白色棋子数量再次--
rowCntW[curX]--; // 行的白色棋子数量--
colCntW[curY]--; // 列的白色棋子数量--
}
}
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++) {
if (grid[i][j] == 1) {rowCntB[i]++; colCntB[j]++;} // 第i行多了一个黑棋,第j列多了一个黑棋
else if (grid[i][j] == 0) {rowCntW[i]++; colCntW[j]++;} // 第i行多了一个白棋,第j列多了一个白棋
if (grid[i][j] == -1) {
coordinateX[len] = i;
coordinateY[len++] = j;
}
}
dfs (0); // 从坐标0开始搜索
// for (int i = 0; i < N; i++) {
// for (int j = 0; j < N; j++) cout << grid[i][j] << ' ';
// cout << '\n';
// }
for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) cout << grid[i][j];
cout << '\n';
return 0;
}
2-洛谷P12266-不同的分值-蓝桥杯2024国PythonB
【题目】
题目描述
在今年蓝桥杯的决赛中,一共有 10 10 10 道题目,每道题目的分数依次为 5 5 5 分, 5 5 5 分, 10 10 10 分, 10 10 10 分, 15 15 15 分, 15 15 15 分, 20 20 20 分, 20 20 20 分, 25 25 25 分, 25 25 25 分。
假设某位参赛选手在解答每一道题时,要么能得到该题的全部分数,要么就得 0 0 0 分。那么请问,这位参赛选手在完成这 10 10 10 道题之后,所能获得的总分值存在多少种不同的情况?
注意,总分值仅需关注选手 10 10 10 道题的总得分,而无需关注具体是由哪些题获得了相应的分数。例如,选手第一道题获得 5 5 5 分其余题均为 0 0 0 分,与第二道题获得 5 5 5 分其余题均为 0 0 0 分,应视为同一种情况。
输出格式
这是一道结果填空的题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只需要编写一个程序输出这个整数,输出多余的内容将无法得分。
【思路】
简单的一道题,只需要套DFS模板即可
注意一点:求的是有多少个不同的分值,而不是多少个不同的情况
【代码】
cpp
/* 2026-04-01-03-算法打卡day37-DFS专项训练4-全部是蓝桥杯真题
* 2-洛谷P12266-不同的分值-蓝桥杯2024国PythonB
* Author:郑龙浩
* Date:2026-04-04
* 算法:
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
unordered_set <int> visited; // 存储曾经计算过的总分值
int cnt = 0, nums[10] = {5, 5, 10, 10, 15, 15, 20, 20, 25, 25}, score = 0;
void dfs(int pos) {
if (pos == 10) {
if (visited.find(score) == visited.end()) {
visited.insert(score);
cnt++;
}
return;
}
// 1 填充0
score += 0;
dfs(pos + 1);
score -= 0; // 回溯
// 2 填充分数
score += nums[pos];
dfs(pos + 1);
score -= nums[pos]; // 回溯
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
dfs(0);
cout << cnt;
return 0;
}
3-洛谷P10386-五子棋对弈-蓝桥杯2024省A
【题目】
题目描述
"在五子棋的对弈中,友谊的小船说翻就翻?" 不!对小蓝和小桥来说,五子棋不仅是棋盘上的较量,更是心与心之间的沟通。这两位挚友秉承着 "友谊第一,比赛第二" 的宗旨,决定在一块 5 × 5 5 × 5 5×5 的棋盘上,用黑白两色的棋子来决出胜负。但他们又都不忍心让对方失落,于是决定用一场和棋(平局) 作为彼此友谊的见证。
比赛遵循以下规则:
- 棋盘规模:比赛在一个 5 × 5 5 × 5 5×5 的方格棋盘上进行,共有 25 25 25 个格子供下棋使用。
- 棋子类型:两种棋子,黑棋与白棋,代表双方。小蓝持白棋,小桥持黑棋。
- 先手规则:白棋(小蓝)具有先手优势,即在棋盘空白时率先落子(下棋)。
- 轮流落子:玩家们交替在棋盘上放置各自的棋子,每次仅放置一枚。
- 胜利条件:率先在横线、竖线或斜线上形成连续的五个同色棋子的一方获胜。
- 平局条件:当所有 25 25 25 个棋盘格都被下满棋子,而未决出胜负时,游戏以平局告终。
在这一设定下,小蓝和小桥想知道,有多少种不同的棋局情况(终局不同看成不同情况,终局相同而落子顺序不同看成同一种情况),既确保棋盘下满又保证比赛结果为平局。
输入格式
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
输出格式
这是一道结果填空题,你只需要算出结果后提交即可。本题的结果为一个整数,在提交答案时只填写这个整数,填写多余的内容将无法得分。
【思路】
【代码】
cpp
/* 2026-04-01-03-算法打卡day37-DFS专项训练4-全部是蓝桥杯真题
* 3-洛谷P10386-五子棋对弈-蓝桥杯2024省A
* Author:郑龙浩
* Date:2026-04-04
* 算法:DFS
* 这道题感觉好难写,大题思路有,代码写起来错了好多,修改了好久
*
* 有一步我没想出来,将棋盘状态转换为string,然后存入set中,这样就可以保证存储了每一种情况,并且是满足终局相同而落子顺序不同视为一种情况
*
* 这个写法严重超时了,AI告诉我需要搜索空间25! ≈ 1.55×10^25,太大,普通计算机无法计算出来
*
* 但是逻辑上是正确的, 这道题还是太难了,我放弃了,就写成这样吧
*
* 答案是5640
*
* 我就当练手了,就当练习DFS了,这道题好像要用数学思维计算
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int grid[5][5] = {0};
int cnt = 0; // 方案数量
unordered_set <string> results; // 存储每种方案的棋盘
// 将棋盘状态存储为string
string toStr() {
string s;
for(int i = 0; i < 5; i++) for (int j = 0; j < 5; j++)
s.push_back(grid[i][j] + '0');
return s;
}
// 是否连成
bool IS(int color) {
for (int i = 0; i < 5; i++) {
bool f = true; // 假设这一行是连起来了
for (int j = 0; j < 5; j++)
if (color != grid[i][j]) {f = false; break;}
if (f) return true;
f = true; // 假设这一列
for (int j = 0; j < 5; j++)
if (color != grid[j][i]) {f = false; break;}
if (f) return true;
}
// 对角线1
bool f = true;
for (int i = 0; i < 5; i++)
if (grid[i][i] != color) {f = false; break;}
if (f) return true;
// 对角线2
f = true;
for (int i = 0; i < 5; i++)
if (grid[i][4 - i] != color) {f = false; break;}
if (f) return true;
return false;
}
// step是指的第几步,奇数是白,偶数是黑
void dfs(int step) {
if (step == 26) {
// 检查是否平局
if (!IS(1) && !IS(2)) {
results.insert(toStr()); // 将方案存入
}
return;
}
int color; if (step % 2 == 1) color = 1;
else color = 2;
// 寻找在step步可以填入的color棋子的位置
for (int i = 0; i < 5; i++) for (int j = 0; j < 5; j++) {
if (grid[i][j] > 0) continue; // 如果当前位置被填充过了,直接跳过,不需要填充了
grid[i][j] = color;
// 只有在填充color棋子后依然没有连成5个的情况才能继续向step步填充棋子
if (IS(color) == false) dfs(step + 1);
grid[i][j] = 0; // 回溯
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
dfs(1);
cout << results.size();
return 0;
}
2026-04-01-04-算法打卡day37-枚举专项训练1-全部是蓝桥杯真题
1-洛谷P12138-寻找质数-蓝桥杯2025省A
【题目】
如果一个正整数只能被 1 和它本身两个数整除,就称为一个质数。最小的几个质数依次是 2,3,5,7,11,13,⋯
请问,第 2025 个质数是多少?
【思路】
普通枚举
【代码】
cpp
/* 2026-04-01-03-算法打卡day37-DFS专项训练4-全部是蓝桥杯真题
* 1-洛谷P12138-寻找质数-蓝桥杯2025省A
* Author:郑龙浩
* Date:2026-04-04
* 算法:
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
bool IS(ll num) {
for (int i = 2; i * i <= num; i++) {
if (num % i == 0) return false;
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int cnt = 0;
for (ll i = 2;; i++) {
if (IS(i)) cnt++;
if (cnt == 2025) {cout << i;break;}
}
return 0;
}
2-洛谷P8680-特别数的和-蓝桥杯2019省B
【题目】
题目描述
小明对数位中含有 2 2 2、 0 0 0、 1 1 1、 9 9 9 的数字很感兴趣(不包括前导 0 0 0),在 1 1 1 到 40 40 40 中这样的数包括 1 1 1、 2 2 2、 9 9 9、 10 10 10 至 32 32 32、 39 39 39 和 40 40 40,共 28 28 28 个,他们的和是 574 574 574。
请问,在 1 1 1 到 n n n 中,所有这样的数的和是多少?
输入格式
输入一行包含一个整数 n n n。
输出格式
输出一行,包含一个整数,表示满足条件的数的和。
输入输出样例 #1
输入 #1
40
输出 #1
574
说明/提示
对于 20 % 20\% 20% 的评测用例, 1 ≤ n ≤ 10 1 \le n \le 10 1≤n≤10。
对于 50 % 50\% 50% 的评测用例, 1 ≤ n ≤ 100 1 \le n \le 100 1≤n≤100。
对于 80 % 80\% 80% 的评测用例, 1 ≤ n ≤ 1000 1 \le n \le 1000 1≤n≤1000。
对于所有评测用例, 1 ≤ n ≤ 10000 1 \le n \le 10000 1≤n≤10000。
蓝桥杯 2019 省赛 B 组 F 题。
【思路】
【代码】
cpp
/* 2026-04-04-算法打卡day37-枚举专项训练-全部是蓝桥杯真题
* 3-P10984-残缺的数字-蓝桥杯2023国PythonAJavaA
* Author:郑龙浩
* Date:2026-04-04
* 算法:
*/
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string strs[] = {"1111110", "0110000", "1101101", "1111001", "0110011", "1011011", "1011111", "1110000", "1111111", "1111011"};
// 如何判断有哪几个可能的状态码?我的想法是:
// 找出s中为1的索引是哪些个,然后去找strs中这些索引为1的码的数量,这个就是所有可能的情况
int Cnt(string& s) {
int len = 0;
int OneIndexs[7] = {0};
// 找出索引
for (int i = 0; i < 7; i++) {
if (s[i] == '1') OneIndexs[len++] = i;
}
int ans = 0;
for (int i = 0; i < 10; i++) {
bool f = true;
for (int j = 0; j < len; j++)
if (strs[i][OneIndexs[j]] != '1') {f = 0; break;}
if (f) ans++;
}
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
string ss[18] = {"0000011", "1001011", "0000001", "0100001", "0101011", "0110110", "1111111", "0010110", "0101001", "0010110", "1011100", "0100110", "1010000", "0010011", "0001111", "0101101", "0110101", "1101010"};
ll ans = 1;
for (int i = 0; i < 18; i++) {
int cnt = Cnt(ss[i]);
if (cnt != 0) ans *= cnt;
}
cout << ans;
return 0;
}