指导老师 :毛明松编撰 :衷铭川(大数据 231 班,程设协会负责人)友链 :其实连按部就班也比想象中难 江西财经大学信息管理与数学学院编程会、计算机与人工智能学院程序设计竞赛协会
# 搜索训练的意义在蓝桥杯竞赛中,搜索是一种强大的工具,解决诸如路径查找、排列组合、状态转移的问题,遍历图或树的所有可能路径。 有时,暴力搜索会直接爆炸 T L E TLE T L E ,剪枝 也是搜索中不可或缺的一环。
# 题目题目集链接 :https://vjudge.net/contest/700913#overview 题目集密码 :d u o x i c h a n g a n duoxichangan d u o x i c h a n g a n V J VJ V J 比较抽象,可能需要~~ 魔法 ~~科学上网排行榜链接 :https://vjudge.net/contest/700913#rank
# 全排列问题 # 题目背景按照字典序输出自然数 1 1 1 到 n n n 所有不重复的排列,即 n n n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。
# 输入格式一个整数 n n n .
# 输出格式由 1 ∼ n 1 \sim n 1 ∼ n 组成的所有不重复的数字序列,每行一个序列。 每个数字保留 5 5 5 个场宽。
# 数据范围1 ≤ n ≤ 9 1 \leq n \leq 9 1 ≤ n ≤ 9 。
# 解题思路观察可知,题目数据范围及小,就算暴力枚举所有情况,情况数也才 9 ! 9! 9 ! 种,故考虑暴力搜索。
c++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int n, a[10 ], num[10 ];bool st[10 ];void dfs (int u) { if (u == n + 1 ) { for (int i = 1 ; i <= n; i++) printf ("%5d" , num[i]); puts ("" ); return ; } for (int i = 1 ; i <= n; i++) if (!st[i]) num[u] = i, st[i] = 1 , dfs (u + 1 ), st[i] = 0 ; } void solve () { cin >> n; dfs (1 ); }
# Count Simple Paths # 题目背景有一个由 H × W H \times W H × W 个单元格组成的网格。让 ( i , j ) (i, j) ( i , j ) 表示从上往下第 i i i 行,从左往上第 j j j 列的单元格。 如果 S i , j S_{i,j} S i , j 是 .
,则单元格 ( i , j ) (i, j) ( i , j ) 为空;如果是 #
,则单元格 ( i , j ) (i, j) ( i , j ) 被阻塞。 计算从一个空单元格开始,向相邻单元格(向上、向下、向左或向右)进行 K K K 移动,而不经过被阻塞的方格,也不多次访问同一单元格的方法的数目。 具体地说,计算满足以下条件的长度为 K + 1 K+1 K + 1 , ( ( i 0 , j 0 ) , ( i 1 , j 1 ) , … , ( i K , j K ) ) ((i_0, j_0), (i_1, j_1), \dots, (i_K, j_K)) ( ( i 0 , j 0 ) , ( i 1 , j 1 ) , … , ( i K , j K ) ) 的序列的个数。
1 ≤ i k ≤ H 1 \leq i_k \leq H 1 ≤ i k ≤ H 、 1 ≤ j k ≤ W 1 \leq j_k \leq W 1 ≤ j k ≤ W 和 S i k , j k S_{i_k, j_k} S i k , j k 为 ".",每个 0 ≤ k ≤ K 0 \leq k \leq K 0 ≤ k ≤ K 为 .
∣ i k + 1 − i k ∣ + ∣ j k + 1 − j k ∣ = 1 |i_{k+1} - i_k| + |j_{k+1} - j_k| = 1 ∣ i k + 1 − i k ∣ + ∣ j k + 1 − j k ∣ = 1 为每个 0 ≤ k ≤ K − 1 0 \leq k \leq K-1 0 ≤ k ≤ K − 1 。每个 0 ≤ k < l ≤ K 0 \leq k < l \leq K 0 ≤ k < l ≤ K 的 ( i k , j k ) ≠ ( i l , j l ) (i_k, j_k) \neq (i_l, j_l) ( i k , j k ) = ( i l , j l ) . # 数据范围1 ≤ H , W ≤ 10 1 \leq H, W \leq 10 1 ≤ H , W ≤ 1 0 .1 ≤ K ≤ 11 1 \leq K \leq 11 1 ≤ K ≤ 1 1 .H H H , W W W , and K K K are integers.Each S i , j S_{i,j} S i , j is .
or #
. # 解题思路枚举每一个出发点,向四个方向中可以走的方向搜索,只要步数到达 k k k 次,那么 a n s ans a n s 就加上 1 1 1 .
python 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 dx = [1 , -1 , 0 , 0 ] dy = [0 , 0 , 1 , -1 ] s = ['' for _ in range (20 )] flag = [[0 for _ in range (11 )] for _ in range (11 )] res = 0 (h, w, k) = map (int , input ().split()) def dfs (x, y, t ): global res if t == k: res += 1 return for i in range (4 ): xx = x + dx[i] yy = y + dy[i] if (xx >= 0 and xx < h and yy >= 0 and yy < w and s[xx][yy] == '.' and flag[xx][yy] == 0 ): flag[xx][yy] = 1 dfs(xx, yy, t + 1 ) flag[xx][yy] = 0 for i in range (h): s[i] = input () for i in range (h): for j in range (w): if s[i][j] == '.' : flag[i][j] = 1 dfs(i, j, 0 ) flag[i][j] = 0 print (res, end = '' )
# Keep Distance # 题目背景给你整数 N N N 和 M M M . 请按词序打印所有长度为 N N N 的整数序列 ( A 1 , A 2 , … , A N ) (A_1, A_2, \ldots, A_N) ( A 1 , A 2 , … , A N ) .
1 ≤ A i 1 \leq A_i 1 ≤ A i .从 2 2 2 到 N N N 的每个整数 i i i 的 A i − 1 + 10 ≤ A i A_{i - 1} + 10 \leq A_i A i − 1 + 1 0 ≤ A i . A N ≤ M A_N \leq M A N ≤ M .什么是词典顺序? 长度为 N N N 的序列 S = ( S 1 , S 2 , … , S N ) S = (S_1, S_2, \ldots, S_N) S = ( S 1 , S 2 , … , S N ) 在词典顺序上小于长度为 N N N 的序列 T = ( T 1 , T 2 , … , T N ) T = (T_1, T_2, \ldots, T_N) T = ( T 1 , T 2 , … , T N ) ,当且仅当存在一个整数 1 ≤ i ≤ N 1 \leq i \leq N 1 ≤ i ≤ N 使得以下两个条件都成立:
( S 1 , S 2 , … , S i − 1 ) = ( T 1 , T 2 , … , T i − 1 ) (S_1, S_2, \ldots, S_{i-1}) = (T_1, T_2, \ldots, T_{i-1}) ( S 1 , S 2 , … , S i − 1 ) = ( T 1 , T 2 , … , T i − 1 ) .S i S_i S i 小于 T i T_i T i (数字).# 数据范围2 ≤ N ≤ 12 2 \leq N \leq 12 2 ≤ N ≤ 1 2 .10 N − 9 ≤ M ≤ 10 N 10N - 9 \leq M \leq 10N 1 0 N − 9 ≤ M ≤ 1 0 N .# 解题思路若第 i i i 层时的 A i A_i A i 值为 d d d ,那么下一层的可能取 [ d + 10 , M − 10 ( n − i ) ] [d+10,~M-10(n-i)] [ d + 1 0 , M − 1 0 ( n − i ) ] . 我们将这些可能性枚举深搜下一层,用一个 k k k 数组来存当前的状态。 当搜到 N N N 层时,如果满足题目条件,则就将 k k k 的 N 个元素存入答案,否则直接返回,下一次的搜索会覆盖原先的 k i k_i k i .
c++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int n, m, sum, ans[1000000 ][13 ], k[13 ];void dfs (int i, int d) { if (i > n) { sum++; for (int j = 1 ; j <= n; j++) ans[sum][j] = k[j]; } else { for (int j = d; j + (n - i) * 10 <= m; j++) { k[i] = j; dfs (i + 1 , j + 10 ); } } } void solve () { cin >> n >> m; dfs (1 , 1 ); for (int i = 1 ; i <= sum; i++) { for (int j = 1 ; j <= n; j++) cout << ans[i][j] << " " ; cout << endl; } }
# 八皇后 # 题目背景一个如下的 6 × 6 6 \times 6 6 × 6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。 上面的布局可以用序列 2 4 6 1 3 5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5 来描述,第 i i i 个数字表示在第 i i i 行的相应位置有一个棋子,如下: 行号 1 2 3 4 5 6 1\ 2\ 3\ 4\ 5\ 6 1 2 3 4 5 6 列号 2 4 6 1 3 5 2\ 4\ 6\ 1\ 3\ 5 2 4 6 1 3 5 这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。 并把它们以上面的序列方法输出,解按字典顺序排列。 请输出前 3 3 3 个解。最后一行是解的总个数。
# 输入格式一行一个正整数 n n n ,表示棋盘是 n × n n \times n n × n 大小的。
# 输出格式前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
# 数据范围对于 100 % 100\% 1 0 0 % 的数据,6 ≤ n ≤ 13 6 \le n \le 13 6 ≤ n ≤ 1 3 .
# 解题思路题目有以下要求:
每行只能有一个 每列只能有一个 每条对角线(包括两条主对角线的所有平行线)上至多(可以没有)有一个棋子 前两个要求很容易就达成了,只需要从第 1 1 1 行开始,搜到第 n n n 行(显然行不会重复)。 枚举 a[i]
(代表在第 i i i 行放在第 a i a_i a i 列), a[i]
需要是之前没被枚举过的(显然列也不会重复)。 因为要放的棋子是 n n n 个,所以前两个要求便被满足了。
那么第三个要求要怎么满足呢?我们不妨把行列坐标画一下: 观察有,同一条正对角线(左上到右下)的行列之和相等,反对角线(右上到左下)的行列数满足:总行数 - 行 + 列 + i i i (第 i i i 列)相等,故由此衍生出代码:
c++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 int n, a[14 ], hang[14 ], dg[27 ], udg[27 ], cnt = 0 ;bool st[14 ];void dfs (int u) { if (u == n + 1 ) { cnt++; if (cnt <= 3 ) { for (int i = 1 ; i <= n; i++) cout << a[i] << " " ; cout << endl; } } for (int i = 1 ; i <= n; i++) { if (!st[i] && !dg[u + i] && !udg[n - u + i]) { st[i] = dg[u + i] = udg[n - u + i] = 1 , a[u] = i; dfs (u + 1 ); st[i] = dg[u + i] = udg[n - u + i] = 0 ; } } } void solve () { cin >> n; dfs (1 ); cout << cnt << endl; }
# Minimum XOR Path # 题目背景给你一个简单相连的无向图,图中有 N N N 个顶点,编号为 1 1 1 到 N N N ,有 M M M 条边,编号为 1 1 1 到 M M M 。边 i i i 连接顶点 u i u_i u i 和 v i v_i v i ,其标签为 w i w_i w i . 在从顶点 1 1 1 到顶点 N N N 的所有简单路径(不经过同一顶点多次的路径)中,求路径上边的标签的最小异或值。
关于位向 XOR 对于非负整数 a a a 和 b b b ,a ⊕ b a \oplus b a ⊕ b 的定义如下: 在 a ⊕ b a \oplus b a ⊕ b 的二进制表示中,当且仅当 a a a 和 b b b 的 2 k 2^k 2 k 位上的数字中正好有一位是 1 1 1 时, 2 k 2^k 2 k 位( k ≥ 0 k \ge 0 k ≥ 0 )上的数字是 1 1 1 ;否则,它是 0 0 0 . 例如, 3 ⊕ 5 = 6 3 \oplus 5 = 6 3 ⊕ 5 = 6 (二进制为 011 ⊕ 101 = 110 011 \oplus 101 = 110 0 1 1 ⊕ 1 0 1 = 1 1 0 )。 一般来说,对于 k k k 非负整数 x 1 , x 2 , … , x k x_1, x_2, \ldots, x_k x 1 , x 2 , … , x k ,它们的比特 XOR x 1 ⊕ x 2 ⊕ ⋯ ⊕ x k x_1 \oplus x_2 \oplus \cdots \oplus x_k x 1 ⊕ x 2 ⊕ ⋯ ⊕ x k 定义为 ( ⋯ ( ( x 1 ⊕ x 2 ) ⊕ x 3 ) ⊕ ⋯ ) ⊕ x k (\cdots((x_1 \oplus x_2) \oplus x_3) \oplus \cdots) \oplus x_k ( ⋯ ( ( x 1 ⊕ x 2 ) ⊕ x 3 ) ⊕ ⋯ ) ⊕ x k ,这与 x 1 , x 2 , … , x k x_1, x_2, \ldots, x_k x 1 , x 2 , … , x k 的阶数无关。 可以证明,在这个问题的约束条件下,可能的值是有限的。
# 数据范围2 ≤ N ≤ 10 {2} \le {N} \le {10} 2 ≤ N ≤ 1 0 N − 1 ≤ M ≤ N ( N − 1 ) 2 {N-1} \le {M} \le {\frac{N(N-1)}{2}} N − 1 ≤ M ≤ 2 N ( N − 1 ) 1 ≤ u i ≤ v i ≤ N {1} \le {u_i} \le {v_i} \le {N} 1 ≤ u i ≤ v i ≤ N 0 ≤ w i < 2 60 {0} \le {w_i} < {2^{60}} 0 ≤ w i < 2 6 0
# 解题思路看起来这题好像是最短路?其实不然。 观察到,数据范围极小,为什么不用搜索 呢? 其实这题就是一道套了层图论 皮的搜索水题,维护当前搜到的所有边的异或值,继续往当前点能走到的点继续搜即可。 有一个细节需要注意,初始化时 1 e 18 1e18 1 e 1 8 还是太小了
c++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int n, m;bool st[20 ];vector<PII> graph[14 ]; int ans = LONG_LONG_MAX;void dfs (int u, int res) { if (u == n) { ans = min (ans, res); return ; } for (auto t : graph[u]) { int v = t.first, w = t.second; if (st[v]) continue ; st[v] = 1 ; dfs (v, res ^ w); st[v] = 0 ; } }
# 数的划分 # 题目背景将整数 n n n 分成 k k k 份,且每份不能为空,任意两个方案不相同(不考虑顺序)。 例如:n = 7 n=7 n = 7 ,k = 3 k=3 k = 3 ,下面三种分法被认为是相同的。1 , 1 , 5 1,1,5 1 , 1 , 5 ;1 , 5 , 1 1,5,1 1 , 5 , 1 ;5 , 1 , 1 5,1,1 5 , 1 , 1 . 问有多少种不同的分法。
# 输入格式n , k n,k n , k (6 < n ≤ 200 6<n \le 200 6 < n ≤ 2 0 0 ,2 ≤ k ≤ 6 2 \le k \le 6 2 ≤ k ≤ 6 )
# 输出格式1 1 1 个整数,即不同的分法。
# 解题思路题目要求:方案必须不相同 。 可以发现,答案一定是一个单调不减序列,则我们在搜索的时候可以有以下规律:
a j − 1 ≤ a j ≤ ⌊ n − ∑ i = 1 j − 1 a i k − j − 1 ⌋ a_{j-1} \le {a_j} \le \lfloor\frac{n-\sum\limits_{i=1}^{j-1} a_i}{k-j-1}\rfloor a j − 1 ≤ a j ≤ ⌊ k − j − 1 n − i = 1 ∑ j − 1 a i ⌋
则由此可以得出以下代码:
c++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int n, k;int dfs (int n, int k, int u) { if (k == 1 ) return 1 ; int sum = 0 ; for (int i = u; i <= n / k; i++) sum += dfs (n - i, k - 1 , i); return sum; } void solve () { cin >> n >> k; cout << dfs (n, k, 1 ); }
# Stone XOR # 题目描述# 问题陈述有 N N N 个袋子,分别标有袋子 1 1 1 、袋子 2 2 2 、 … \ldots … 、袋子 N N N . 袋子 i i i ( 1 ≤ i ≤ N 1 \leq i \leq N 1 ≤ i ≤ N )装有 A i A_i A i 颗石子。 高桥可以进行以下任意次数的操作,可能为零:
选择两个袋子 A A A 和 B B B ,将 A A A 袋的所有棋子移入 B B B 袋。
问:在重复操作后,求以下不同可能值的个数。
B 1 ⊕ B 2 ⊕ ⋯ ⊕ B N B_1 \oplus B_2 \oplus \cdots \oplus B_N B 1 ⊕ B 2 ⊕ ⋯ ⊕ B N ,其中 B i B_i B i 是袋子 i i i 中石头的最终数量。关于位向 XOR 对于非负整数 a a a 和 b b b ,a ⊕ b a \oplus b a ⊕ b 的定义如下: 在 a ⊕ b a \oplus b a ⊕ b 的二进制表示中,当且仅当 a a a 和 b b b 的 2 k 2^k 2 k 位上的数字中正好有一位是 1 1 1 时, 2 k 2^k 2 k 位( k ≥ 0 k \ge 0 k ≥ 0 )上的数字是 1 1 1 ;否则,它是 0 0 0 . 例如, 3 ⊕ 5 = 6 3 \oplus 5 = 6 3 ⊕ 5 = 6 (二进制为 011 ⊕ 101 = 110 011 \oplus 101 = 110 0 1 1 ⊕ 1 0 1 = 1 1 0 )。 一般来说,对于 k k k 非负整数 x 1 , x 2 , … , x k x_1, x_2, \ldots, x_k x 1 , x 2 , … , x k ,它们的比特 XOR x 1 ⊕ x 2 ⊕ ⋯ ⊕ x k x_1 \oplus x_2 \oplus \cdots \oplus x_k x 1 ⊕ x 2 ⊕ ⋯ ⊕ x k 定义为 ( ⋯ ( ( x 1 ⊕ x 2 ) ⊕ x 3 ) ⊕ ⋯ ) ⊕ x k (\cdots((x_1 \oplus x_2) \oplus x_3) \oplus \cdots) \oplus x_k ( ⋯ ( ( x 1 ⊕ x 2 ) ⊕ x 3 ) ⊕ ⋯ ) ⊕ x k ,这与 x 1 , x 2 , … , x k x_1, x_2, \ldots, x_k x 1 , x 2 , … , x k 的阶数无关。 可以证明,在这个问题的约束条件下,可能的值是有限的。
# 数据范围2 ≤ N ≤ 12 {2} \le {N} \le {12} 2 ≤ N ≤ 1 2 .1 ≤ A i ≤ 1 0 17 {1} \le {A_i} \le {10^{17}} 1 ≤ A i ≤ 1 0 1 7 # 解题思路在解这题的之前你需要知道
异或概念(题目中有描述,此处不再赘述) 这个公式: a ^ b ^ b = a
接下来就可以开始写代码了.
对于第 i i i 个盒子,你可以在前 i − 1 i-1 i − 1 个盒子中,任选盒子进行操作。 注意,此题用 map
会被卡成 T L E TLE T L E ,应该用 unordered_map
(可自行了解二区别) c++ 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 int n, a[13 ], ans = 0 ;unordered_map<int , int > mp; void dfs (int u, int res) { if (u == n + 1 ) { if (!mp[res]) ans++; mp[res] = 1 ; return ; } dfs (u + 1 , res ^ a[u]); for (int i = 1 ; i < u; i++) if (a[i] != 0 ) { int x = a[i]; a[i] = 0 , a[u] += x; dfs (u + 1 , res ^ a[u] ^ x); a[i] = x, a[u] -= x; } }