DSF要不要恢复现场,要看题目是什么样子了,看是内部搜索还是外部搜索
如果有所点都在棋盘内部搜索,我们需要保证每个点只能被搜一次,因此就不需要恢复现场。
如果把棋盘当作一个整体的状态,看棋盘不同状态的变化,就需要恢复现场了。
DFS之联通性模型:【内部搜索】
题目:
一天Extense在森林里探险的时候不小心走入了一个迷宫,迷宫可以看成是由 n∗n 的格点组成,每个格点只有2种状态,.和#,
前者表示可以通行后者表示不能通行。 同时当Extense处在某个格点时,他只能移动到东南西北(或者说上下左右)四个方向之一的相邻格点上,Extense想要从点A走到点B,
问在不走出迷宫的情况下能不能办到。 如果起点或者终点有一个不能通行(为#),则看成无法办到。 注意:A、B不一定是两个不同的点。 输入格式 第1行是测试数据的组数 k,后面跟着 k 组输入。 每组测试数据的第1行是一个正整数 n,表示迷宫的规模是 n∗n 的。 接下来是一个 n∗n 的矩阵,矩阵中的元素为.或者#。 再接下来一行是 4 个整数 ha,la,hb,lb,描述 A 处在第 ha 行, 第 la 列,B 处在第 hb 行, 第 lb 列。 注意到 ha,la,hb,lb 全部是从 0 开始计数的。 输出格式 k行,每行输出对应一个输入。 能办到则输出“YES”,否则输出“NO”。 数据范围 1≤n≤100 输入样例: 2 3 .## ..# #.. 0 0 2 2 5 ..... ###.# ..#.. ###.. ...#. 0 0 4 0 输出样例: YES NO
分析:
简单的DFS搜索路径问题,DFS只可以说明是否存在路径,但是不能说明最短路。 在写DFS的时候,还是要想象它的递归搜索树的样子,这更形象,更具体。
代码:
#include <cstdio> using namespace std; const int N = 110; int n; char g[N][N]; int sx, sy, ex, ey; bool st[N][N]; int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; bool dfs(int sx, int sy) { if (g[sx][sy] == '#') return false; if (sx == ex && sy == ey) return true; st[sx][sy] = true; for (int i = 0; i < 4; i ++ ) { int x = sx + dx[i], y = sy + dy[i]; if (x < 0 || x >= n || y < 0 || y >= n) continue;//我就有个疑惑了,这里return false 和continue有什么区别 // if (x < 0 || x >= n || y < 0 || y >= n) return false;//这里,如果return false,直接返回给了调用它的函数,而不会是一个状态的末尾 if (st[x][y]) continue; // printf("sx: %d, sy: %d || x: %d, y: %d ", sx, sy, x, y); if (dfs(x, y)) return true; } return false; } void work() { scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%s", g[i]); scanf("%d%d%d%d", &sx, &sy, &ex, &ey); // for (int i = 0; i < n; i ++ ) printf("%s ", g[i]); for (int i = 0; i <= n; i ++ ) for (int j = 0; j <= n; j ++ ) st[i][j] = false; if (dfs(sx, sy)) puts("YES"); else puts("NO"); return; } int main() { int T; scanf("%d", &T); while (T -- ) { work(); } return 0; }
题目:
有一间长方形的房子,地上铺了红色、黑色两种颜色的正方形瓷砖。 你站在其中一块黑色的瓷砖上,只能向相邻(上下左右四个方向)的黑色瓷砖移动。 请写一个程序,计算你总共能够到达多少块黑色的瓷砖。 输入格式 输入包括多个数据集合。 每个数据集合的第一行是两个整数 W 和 H,分别表示 x 方向和 y 方向瓷砖的数量。 在接下来的 H 行中,每行包括 W 个字符。每个字符表示一块瓷砖的颜色,规则如下 1)‘.’:黑色的瓷砖; 2)‘#’:红色的瓷砖; 3)‘@’:黑色的瓷砖,并且你站在这块瓷砖上。该字符在每个数据集合中唯一出现一次。 当在一行中读入的是两个零时,表示输入结束。 输出格式 对每个数据集合,分别输出一行,显示你从初始位置出发能到达的瓷砖数(记数时包括初始位置的瓷砖)。 数据范围 1≤W,H≤20 输入样例: 6 9 ....#. .....# ...... ...... ...... ...... ...... #@...# .#..#. 0 0 输出样例: 45
分析:
这里面的,有返回值的DFS还是理解的不到位的,还要再看看的。
代码:
#include <cstdio> using namespace std; const int N = 30; int m, n; char g[N][N]; int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; bool st[N][N]; int dfs(int sx, int sy) { int ans = 1; st[sx][sy] = true; for (int i = 0; i < 4; i ++ ) { int x = sx + dx[i], y = sy + dy[i]; if (x < 0 || x >= n || y < 0 || y >= m) continue; if (st[x][y]) continue; if (g[x][y] == '#') continue; // printf("before:x= %d || y= %d || ans= %d ", x, y, ans); ans += dfs(x, y); // printf("after:x: %d || y= %d || ans= %d ", x, y, ans); } // printf("last: x= %d || y = %d ", x, y); // printf("last: ans= %d ", ans); return ans; } void work() { for (int i = 0; i < n; i ++ ) scanf("%s", g[i]); for (int i = 0; i <= n; i ++ ) for (int j = 0; j <= m; j ++ ) st[i][j] = false; // for (int i = 0; i < n; i ++ ) printf("%s ", g[i]); for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) if (g[i][j] == '@') { printf("%d ", dfs(i, j)); return; } } int main() { while (~scanf("%d%d", &m, &n)) { if (!n && !m) break; work(); } return 0; }
【从这开始就是外部搜索了】
外部搜索:采取一个搜索顺序,使得我们可以将所有方案全部枚举到。
恢复现场:我下去的时候什么样子,回来的时候要还是什么样子
DFS之搜索顺序:
题目:
马在中国象棋以日字形规则移动。 请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。 输入格式 第一行为整数 T,表示测试数据组数。 每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标 n,m,x,y。 输出格式 每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,若无法遍历棋盘上的所有点则输出 0。 数据范围 1≤T≤9, 1≤m,n≤9, 0≤x≤n−1, 0≤y≤m−1 输入样例: 1 5 4 0 0 输出样例: 32
分析:
这一题,让我们计算棋盘有多少种方案数,无疑了,一定需要恢复现场。 当我们到了当前层的时候,将当前层状态标记,然后dfs下面一层,然后紧跟着就要将状态恢复现状。 注意,我们记方案数的时候,就是看叶子节点能否到达我们预设状态,也就是可不可以走完整个棋盘,也就是可不可以拓展n*m个点,如果可以就给他记个方案数。
代码:
#include <cstdio> using namespace std; const int N = 20; int n, m, x, y; bool st[N][N]; int ans; int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2}, dy[8] = {-1, -2, -2, -1, 1, 2, 2, 1}; void dfs(int sx, int sy, int u) { if (u == n * m ) { ans ++; return; } //当前节点向下搜素,将当前节点制成true st[sx][sy] = true; for (int i = 0; i < 8; i ++ ) { int x = dx[i] + sx, y = dy[i] + sy; if (x < 0 || x >= n || y < 0 || y >= m) continue; if (st[x][y]) continue; dfs(x, y, u + 1); } st[sx][sy] = false; } void work() { scanf("%d%d%d%d", &n, &m, &x, &y); for (int i = 0; i <= n; i ++ ) for (int j = 0; j <= m; j ++ ) st[i][j] = false; ans = 0; dfs(x, y, 1); printf("%d ", ans); return; } int main() { int T; scanf("%d", &T); while (T -- ) { work(); } return 0; }
题目:
单词接龙是一个与我们经常玩的成语接龙相类似的游戏。 现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次。 在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish ,如果接成一条龙则变为 beastonish。 我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度,例如 at 和 atide 间不能相连。 输入格式 输入的第一行为一个单独的整数 n 表示单词数,以下 n 行每行有一个单词(只含有大写或小写字母,长度不超过20),输入的最后一行为一个单个字符,表示“龙”开头的字母。 你可以假定以此字母开头的“龙”一定存在。 输出格式 只需输出以此字母开头的最长的“龙”的长度。 数据范围 n≤20 输入样例: 5 at touch cheat choose tact a 输出样例: 23 提示 连成的“龙”为 atoucheatactactouchoose。
分析:
一个字符串可以使用最多2次。 搜的时候,先处理好重叠部分就会很好写了。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 30; int n; string s[N]; int pub[N][N];//两个字符串i与j的最短的公共部分为多少,只有公共部分越短,总字符串才能越长 int cnt[N]; int ans; void dfs(string str, int last) { ans = max(ans, (int)str.size()); cnt[last] ++; for (int i = 0; i < n; i ++ ) if (pub[last][i] && cnt[i] < 2) dfs(str + s[i].substr(pub[last][i]), i); cnt[last] --; } int main() { cin >> n; for (int i = 0; i < n; i ++ ) cin >> s[i]; char c; cin >> c; for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) for (int k = 1; k < (int)min(s[i].size(), s[j].size()); k ++ ) if (s[i].substr(s[i].size() - k, k) == s[j].substr(0, k)) { pub[i][j] = k; break; } for (int i = 0; i < n; i ++ ) if (s[i][0] == c) dfs(s[i], i); cout << ans << " "; return 0; }
题目:
给定 n 个正整数,将它们分组,使得每组中任意两个数互质。 至少要分成多少个组? 输入格式 第一行是一个正整数 n。 第二行是 n 个不大于10000的正整数。 输出格式 一个正整数,即最少需要的组数。 数据范围 1≤n≤10 输入样例: 6 14 20 33 117 143 175 输出样例: 3
分析:
我觉得这题是一道好题,可以很好的那他来练手,练DFS 可以通过多个角度去搜索,可以通过不同的方法去搜索。 可以一组一组的搜,可以一个一个的搜。
可以以每个元素为状态,判断当前元素可以放到那一组中去。
可以以组为状态,判断每个元素剩余元素是否可以放到当前组,(这里的尝试就说明我们需要恢复现场)没有被放入的,如果可以放,就将它尝试放入,、
如果可以放入,那我们就不需要再新开一个数组,如果对于当前数组来说,剩余的所有元素都不能放入当前数组了,那我们才新开一个数组存放。
优先将所有能加的数都加到当前数组,加满为止。
枚举那个数可以放到当前组是一个组合问题,而不是一个排列问题,放入当前数组的元素不需要考虑顺序,所以我们可以搜索状态的时候,可以加一个last
代码:
//先搜第一组,组内没有元素,当前一共搜了0个元素,从0号下标开始搜 //start,对于当前组来说,start前面的元素都被搜索过了,从当前位置开始搜索 #include <cstdio> using namespace std; const int N = 11; int n; int a[N]; int g[N][N]; int ans = N; bool st[N]; int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } bool check(int cnt, int ct, int i) { for (int j = 0; j < ct; j ++ ) if (gcd(g[cnt][j], a[i]) > 1) return false; return true; } void dfs(int u, int cnt, int ct, int last) { if (cnt >= ans) return; if (u == n) { ans = cnt; return; } //枚举所有元素,判断是否可以放入最后一组(当前组),前面的元素都被枚举过了,上一个搜索的位置开始搜last bool flag = true; for (int i = last; i < n; i ++ ) if (!st[i] && check(cnt, ct, i)) { st[i] = true; g[cnt][ct] = a[i]; dfs(u + 1, cnt, ct + 1, i); st[i] = false; flag = false; } //我们的当前组(最后一组)已经没有元素可以加进去了 if (flag) dfs(u, cnt + 1, 0, 0); } int main() { scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]); dfs(0, 1, 0, 0); printf("%d ", ans); return 0; }
#include <cstdio> #include <algorithm> using namespace std; const int N = 11; int n; int a[N]; int g[N][N], p[N]; int gcd(int a, int b) { return b ? gcd(b, a % b) : a; } bool check(int g[], int t, int ct) { for (int i = 0; i < ct; i ++ ) if (gcd(g[i], t) > 1) return false; return true; } int ans = N; int cnt; void dfs(int u) { if (u == n + 1) { ans = min(ans, cnt); return; } //将当前元素尝试加入每一个组 for (int i = 0; i < cnt; i ++ ) if (check(g[i], a[u], p[i])) { g[i][p[i] ++ ] = a[u]; dfs(u + 1); g[i][-- p[i]]; } //尝试为这个元素新开一个组 g[cnt ++ ][p[cnt - 1] ++ ] = a[u]; dfs(u + 1); g[-- cnt][-- p[cnt]]; } int main() { scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); dfs(1); printf("%d ", ans); return 0; }
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int N = 11; int n; ll a[N]; inline ll gcd(ll a, ll b) { return b ? gcd(b, a % b) : a; } int ans = N, cnt; ll g[N]; void dfs(int u) { if (u == n) { ans = min(ans, cnt); return; } for (int i = 0; i < cnt; i ++ ) if (gcd(g[i], a[u]) == 1) { g[i] *= a[u]; dfs(u + 1); g[i] /= a[u]; } g[cnt ++ ] = a[u]; dfs(u + 1); g[-- cnt] /= a[u]; } int main() { scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%lld", a + i); for (int i = 0; i < n; i ++ ) g[i] = 1; dfs(0); printf("%d ", ans); return 0; }
DFS之剪枝与优化:
题目:
翰翰和达达饲养了 N 只小猫,这天,小猫们要去爬山。 经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了(呜咕>_<)。 翰翰和达达只好花钱让它们坐索道下山。 索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C1、C2……CN。 当然,每辆缆车上的小猫的重量之和不能超过 W。 每租用一辆缆车,翰翰和达达就要付 1 美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山? 输入格式 第 1 行:包含两个用空格隔开的整数,N 和 W。 第 2..N+1 行:每行一个整数,其中第 i+1 行的整数表示第 i 只小猫的重量 Ci。 输出格式 输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。 数据范围 1≤N≤18, 1≤Ci≤W≤108 输入样例: 5 1996 1 2 1994 12 29 输出样例: 2
分析:
小猫爬山这题,用DFS来搜索所有状态空间,找到题目所求性质MIN。让车辆的个数最少。 思路:搜索运送的小猫数量作为我们的状态空间。 优化:1.给小猫降序处理,先安放体重较大的。2.如果搜索小车数量大于等于当前答案,及时剪枝,因为这个搜索分支最好的情况也不比ans优。
代码:
#include <cstdio> #include <algorithm> using namespace std; const int N = 20; int n, m; int a[N]; int ans = N; int s[N], cnt; void dfs(int u) { //如果此时的cnt已经大于等于我们的ans了,一定不会比现在的情况更优,可以及时剪枝 if (cnt >= ans) return; if (u == n) { ans = cnt; for (int i = 0; i < cnt; i ++ ) { // printf("%d ", s[i]); } return; } for (int i = 0; i < cnt; i ++ ) if (s[i] + a[u] <= m) { s[i] += a[u]; dfs(u + 1); s[i] -= a[u]; } s[cnt ++ ] = a[u]; dfs(u + 1); s[-- cnt] = 0; } int main() { scanf("%d%d", &n, &m); for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]); sort(a, a + n); reverse(a, a + n); dfs(0); printf("%d ", ans); return 0; }
题目:
数独是一种传统益智游戏,你需要把一个 9×9 的数独补充完整,使得图中每行、每列、每个 3×3 的九宫格内数字 1∼9 均恰好出现一次。 请编写一个程序填写数独。 输入格式 输入包含多组测试用例。 每个测试用例占一行,包含 81 个字符,代表数独的 81 个格内数据(顺序总体由上到下,同行由左到右)。 每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。 您可以假设输入中的每个谜题都只有一个解决方案。 文件结尾处为包含单词 end 的单行,表示输入结束。 输出格式 每个测试用例,输出一行数据,代表填充完全后的数独。 输入样例: 4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4...... ......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3. end 输出样例: 417369825632158947958724316825437169791586432346912758289643571573291684164875293 416837529982465371735129468571298643293746185864351297647913852359682714128574936
分析:
这题,难搞,该多敲
代码:
#include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 9, M = 1 << N; int ones[M], map[M]; int row[N], col[N], cell[3][3]; char str[100]; void init() { for (int i = 0; i < N; i ++ ) row[i] = col[i] = (1 << N) - 1; for (int i = 0; i < 3; i ++ ) for (int j = 0; j < 3; j ++ ) cell[i][j] = (1 << N) - 1; } void draw(int x, int y, int t, bool is_set) { if (is_set) str[x * N + y] = '1' + t; else str[x * N + y] = '.'; int v = 1 << t; if (!is_set) v = -v; row[x] -= v; col[y] -= v; cell[x / 3][y / 3] -= v; } int lowbit(int x) { return x & -x; } int get(int x, int y) { return row[x] & col[y] & cell[x / 3][y / 3]; } bool dfs(int cnt) { if (!cnt) return true; int minv = 10; int x, y; for (int i = 0; i < N; i ++ ) for (int j = 0; j < N; j ++ ) if (str[i * N + j] == '.') { int state = get(i, j); if (ones[state] < minv) { minv = ones[state]; x = i, y = j; } } int state = get(x, y); for (int i = state; i; i -= lowbit(i)) { int t = map[lowbit(i)]; draw(x, y, t, true); if (dfs(cnt - 1)) return true; draw(x, y, t, false); } return false; } int main() { for (int i = 0; i < N; i ++ ) map[1 << i] = i; for (int i = 0; i < 1 << N; i ++ ) for (int j = 0; j < N; j ++ ) ones[i] += i >> j & 1; while (cin >> str, str[0] != 'e') { init(); int cnt = 0; for (int i = 0, k = 0; i < N; i ++ ) for (int j = 0; j < N; j ++, k ++ ) if (str[k] != '.') { int t = str[k] - '1'; draw(i, j, t, true); } else cnt ++ ; dfs(cnt); puts(str); } return 0; }
题目:
乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过 50 个长度单位。 然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。 请你设计一个程序,帮助乔治计算木棒的可能最小长度。 每一节木棍的长度都用大于零的整数表示。 输入格式 输入包含多组数据,每组数据包括两行。 第一行是一个不超过 64 的整数,表示砍断之后共有多少节木棍。 第二行是截断以后,所得到的各节木棍的长度。 在最后一组数据之后,是一个零。 输出格式 为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。 数据范围 数据保证每一节木棍的长度均不大于 50。 输入样例: 9 5 2 1 5 2 1 5 2 1 4 1 2 3 4 0 输出样例: 6 5
分析:
代码:
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int N = 70; int n; int a[N]; int sum, len; bool st[N]; bool dfs(int u, int lens, int start){ // cout << sum << " " << len << " " << u << endl; if (u * len == sum) return true; if (lens == len) return dfs(u + 1, 0, 0); int fail = 0; for (int i = start; i < n; i ++ ) if (!st[i] && a[i] + lens <= len && fail != a[i]) { st[i] = true; if (dfs(u, lens + a[i], i + 1)) return true; st[i] = false; fail = a[i]; if (lens == 0 || lens + a[i] == len) return false; } return false; } int main() { while (cin >> n, n) { sum = 0; for (int i = 0; i < n; i ++ ) { st[i] = 0; scanf("%d", a + i); sum += a[i]; } sort(a, a + n); reverse(a, a + n); for (len = a[n - 1]; len <= sum; len ++ ) if (sum % len == 0) { if (dfs(0, 0, 0)) { printf("%d ", len); break; } } } return 0; }
题目:
7 月 17 日是 Mr.W 的生日,ACM-THU 为此要制作一个体积为 Nπ 的 M 层生日蛋糕,每层都是一个圆柱体。 设从下往上数第 i 层蛋糕是半径为 Ri,高度为 Hi 的圆柱。 当 i<M 时,要求 Ri>Ri+1 且 Hi>Hi+1。 由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积 Q 最小。 令 Q=Sπ ,请编程对给出的 N 和 M,找出蛋糕的制作方案(适当的 Ri 和 Hi 的值),使 S 最小。 除 Q 外,以上所有数据皆为正整数。 输入格式 输入包含两行,第一行为整数 N,表示待制作的蛋糕的体积为 Nπ。 第二行为整数 M,表示蛋糕的层数为 M。 输出格式 输出仅一行,是一个正整数 S(若无解则 S=0)。 数据范围 1≤N≤10000, 1≤M≤20 输入样例: 100 2 输出样例: 68
代码:
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 10010, inf = 1 << 30; int n, m; int minv[N], mins[N]; int r[N], h[N]; int ans = inf; void dfs(int u, int s, int v) { // cout << v << " " << s << endl; if (v + minv[u] > n) return; if (s + mins[u] > ans) return; if (s + 2 * (n - v) / r[u + 1] >= ans) return; if (u < 0) return; if (u == 0) { if (v == n) ans = s; return; } for (int i = min((int)sqrt(n - v), r[u + 1] - 1); i >= u; i -- ) for (int j = min((n - v) / i / i, h[u + 1] - 1); j >= u; j -- ) { int t = 0; if (u == m) t = i * i; r[u] = i, h[u] = j; dfs(u - 1, s + 2 * i * j + t, v + i * i * j); } } int main() { cin >> n >> m; for (int i = 1; i <= n; i ++ ) minv[i] = minv[i - 1] + i * i * i; for (int i = 1; i <= n; i ++ ) mins[i] = mins[i - 1] + 2 * i * i; r[m + 1] = h[m + 1] = inf; dfs(m, 0, 0); if (ans == 1 << 30) cout << 0 << endl; else cout << ans << endl; return 0; }
迭代加深
题目:
满足如下条件的序列 X(序列中元素被标号为 1、2、3…m)被称为“加成序列”: X[1]=1 X[m]=n X[1]<X[2]<…<X[m−1]<X[m] 对于每个 k(2≤k≤m)都存在两个整数 i 和 j (1≤i,j≤k−1,i 和 j 可相等),使得 X[k]=X[i]+X[j]。 你的任务是:给定一个整数 n,找出符合上述条件的长度 m 最小的“加成序列”。 如果有多个满足要求的答案,只需要找出任意一个可行解。 输入格式 输入包含多组测试用例。 每组测试用例占据一行,包含一个整数 n。 当输入为单行的 0 时,表示输入结束。 输出格式 对于每个测试用例,输出一个满足需求的整数序列,数字之间用空格隔开。 每个输出占一行。 数据范围 1≤n≤100 输入样例: 5 7 12 15 77 0 输出样例: 1 2 4 5 1 2 4 6 7 1 2 4 8 12 1 2 4 5 10 15 1 2 4 8 9 17 34 68 77
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <vector> using namespace std; const int N = 110; int n; int a[N]; int depth; bool st[N]; //枚举层数 bool dfs(int u) { if (u > depth) return false; if (u == depth && a[u - 1] == n) return true; //从当前分支来看,如果已经出现过的和就不要在出现了 memset(st, 0, sizeof st); for (int i = u - 1; i >= 0; i -- ) for (int j = i; j >= 0; j -- ) { int t = a[i] + a[j]; if (st[t]) continue; if (t > n) continue; if (a[u - 1] * 2 < t) continue; if (t <= a[u - 1]) continue; st[t] = true; a[u] = t; if (dfs(u + 1)) return true; } return false; } int main() { while (cin >> n, n) { a[0] = 1; for (depth = 1; !dfs(1); depth ++ ); for (int i = 0; i < depth; i ++ ) printf("%d ", a[i]); puts(""); // printf("%d ", depth); } return 0; }
双向DFS
题目:
达达帮翰翰给女生送礼物,翰翰一共准备了 N 个礼物,其中第 i 个礼物的重量是 G[i]。 达达的力气很大,他一次可以搬动重量之和不超过 W 的任意多个物品。 达达希望一次搬掉尽量重的一些物品,请你告诉达达在他的力气范围内一次性能搬动的最大重量是多少。 输入格式 第一行两个整数,分别代表 W 和 N。 以后 N 行,每行一个正整数表示 G[i]。 输出格式 仅一个整数,表示达达在他的力气范围内一次性能搬动的最大重量。 数据范围 1≤N≤46, 1≤W,G[i]≤231−1 输入样例: 20 5 7 5 4 18 1 输出样例: 19
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; const int N = 70; int m, n; int a[N]; int w[1 << 25], cnt; int pos; void dfs(int u, int k, int s) { if (u >= k) { w[cnt ++ ] = s; return; } dfs(u + 1, k, s); if ((LL)a[u] + s <= m) dfs(u + 1, k, s + a[u]); } int ans; void dfs(int u, int s) { if (u >= n) { int l = 0, r = pos - 1; while (l < r) { int mid = l + r + 1>> 1; if ((LL)w[mid] + s <= m) l = mid; else r = mid - 1; } ans = max(ans, w[r] + s); return; } dfs(u + 1, s); if ((LL)a[u] + s <= m) dfs(u + 1, s + a[u]); } int main() { cin >> m >> n; for (int i = 0; i < n; i ++ ) cin >> a[i]; sort(a, a + n); reverse(a, a + n); dfs(0, n / 2 + 2, 0); sort(w, w + cnt); pos = unique(w, w + cnt) - w; // cout << pos << endl; // for (int i = 0; i < pos; i ++ ) cout << w[i] << " "; // cout << endl; dfs(n / 2 + 2, 0); // exit(0); cout << ans << endl; return 0; }
IDA*
题目:
给定 n 本书,编号为 1∼n。 在初始状态下,书是任意排列的。 在每一次操作中,可以抽取其中连续的一段,再把这段插入到其他某个位置。 我们的目标状态是把书按照 1∼n 的顺序依次排列。 求最少需要多少次操作。 输入格式 第一行包含整数 T,表示共有 T 组测试数据。 每组数据包含两行,第一行为整数 n,表示书的数量。 第二行为 n 个整数,表示 1∼n 的一种任意排列。 同行数之间用空格隔开。 输出格式 每组数据输出一个最少操作次数。 如果最少操作次数大于或等于 5 次,则输出 5 or more。 每个结果占一行。 数据范围 1≤n≤15 输入样例: 3 6 1 3 4 6 2 5 5 5 4 3 2 1 10 6 8 5 3 4 7 2 9 1 10 输出样例: 2 3 5 or more
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 20; int n; int a[N]; int depth; int backup[5][N]; int f() { int ans = 0; for (int i = 0; i + 1 < n; i ++ ) if (a[i + 1] != a[i] + 1) ans ++; return (ans + 2) / 3; } bool dfs(int u) { if (u + f() > depth) return false; if (!f()) return true; for (int l = 0; l < n; l ++ ) for (int r = l; r < n; r ++ ) for (int k = r + 1; k < n; k ++ ) { for (int i = 0; i < n; i ++ ) backup[u][i] = a[i]; int x, y; // for (x = r + 1, y = l; x <= k; x ++, y ++ ) a[y] = backup[u][x]; // for (x = l; x <= r; x ++, y ++ ) a[y] = backup[u][x]; for (x = l, y = l + k - r; x <= r; x ++, y ++ ) a[y] = backup[u][x]; for (x = r + 1, y = l; x <= k; x ++, y ++ ) a[y] = backup[u][x]; if (dfs(u + 1)) return true; for (int i = 0; i < n; i ++ ) a[i] = backup[u][i]; } return false; } void work() { cin >> n; for (int i = 0; i < n; i ++ ) cin >> a[i]; for (depth = 0; !dfs(0) && depth < 5; depth ++ ); if (depth >= 5) puts("5 or more"); else cout << depth << endl; return; } int main() { int T; cin >> T; while (T -- ) { work(); } return 0; }
题目:
如下图所示,有一个
#
形的棋盘,上面有 1,2,31,2,3 三种数字各 88 个。给定 88 种操作,分别为图中的 A∼HA∼H。
这些操作会按照图中字母和箭头所指明的方向,把一条长为 77 的序列循环移动 11 个单位。
例如下图最左边的
#
形棋盘执行操作 AA 后,会变为下图中间的#
形棋盘,再执行操作 CC 后会变成下图最右边的#
形棋盘。给定一个初始状态,请使用最少的操作次数,使
#
形棋盘最中间的 88 个格子里的数字相同。输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 2424 个数字,表示将初始棋盘中的每一个位置的数字,按整体从上到下,同行从左到右的顺序依次列出。
输入样例中的第一个测试用例,对应上图最左边棋盘的初始状态。
当输入只包含一个 00 的行时,表示输入终止。
输出格式
每个测试用例输出占两行。
第一行包含所有移动步骤,每步移动用大写字母 A∼HA∼H 中的一个表示,字母之间没有空格,如果不需要移动则输出
No moves needed
。第二行包含一个整数,表示移动完成后,中间 88 个格子里的数字。
如果有多种方案,则输出字典序最小的解决方案。
输入样例:
1 1 1 1 3 2 3 2 3 1 3 2 2 3 1 2 2 2 3 1 2 1 3 3 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 0
输出样例:
AC 2 DDHH 2
代码:
#include <cstdio> #include <cstring> #include <sstream> #include <iostream> #include <algorithm> using namespace std; const int N = 110; //将八个操作的序号记录下来 int op[8][7] = { {0, 2, 6, 11, 15, 20, 22}, {1, 3, 8, 12, 17, 21, 23}, {10, 9, 8, 7, 6, 5, 4}, {19, 18, 17, 16, 15, 14, 13}, {23, 21, 17, 12, 8, 3, 1}, {22, 20, 15, 11, 6, 2, 0}, {13, 14, 15, 16, 17, 18, 19}, {4, 5, 6, 7, 8, 9, 10} }; int center[8] = {6, 7, 8, 11, 12, 15, 16, 17}; int opposite[8] = {5, 4, 7, 6, 1, 0, 3, 2}; int n; int a[N]; int path[N]; int depth; //执行当前操作,移动格子 void work(int u) { int tmp = a[op[u][0]]; for (int i = 1; i < 7; i ++ ) a[op[u][i - 1]] = a[op[u][i]]; a[op[u][6]] = tmp; } int f() { static int cnt[4] = {0}; for (int i = 1; i <= 3; i ++ ) cnt[i] = 0; for (int i = 0; i < 8; i ++ ) { // printf("%d ", a[center[i]]); cnt[a[center[i]]] ++; } // printf(" "); // int ans = max(cnt[1], max(cnt[2], cnt[3])); int ans = 0, id = 0; for (int i = 1; i <= 3; i ++ ) if (ans < cnt[i]) ans = cnt[i], id = i; // printf("id: %d, ans: %d ", id, ans); return 8 - ans; } bool dfs(int u, int last) { // cout << u << " " << last << endl; // cout << f() << endl; if (u + f() > depth) return false; if (!f()) return true; // cout << u << " " << last << endl; for (int i = 0; i < 8; i ++ ) { if (opposite[i] == last) continue; work(i); path[u] = i; if (dfs(u + 1, i)) return true; work(opposite[i]); } return false; } int main() { string s; while (getline(cin, s)) { n = 0; stringstream ssin(s); while (ssin >> a[n]) n ++; if (!a[0]) break; for (depth = 0; !dfs(0, -1); depth ++ ); // exit(0); // puts("=-------------------="); if (!depth) puts("No moves needed"), printf("%d ", a[6]); else { // for (int i = 0; i < depth; i ++ ) cout << path[i] << " "; // cout << endl; for (int i = 0; i < depth; i ++ ) printf("%c", 'A' + path[i]); printf(" %d ", a[6]); } // puts("-________________________________-"); } return 0; }