《挑战程序设计竞赛》第二章
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《挑战程序设计竞赛》第二章相关的知识,希望对你有一定的参考价值。
2.1 最基础的穷竭搜索
poj 2386 Lake Counting(裸dfs)
题意:n*m的矩阵 W是水 .是地 问有多少池塘。(池塘的定义是: W通过八个方向连接成的一片算作是一个池塘。)
1 #include <iostream> 2 using namespace std; 3 #include <cstdio> 4 const int maxn = 100 + 5; 5 int n, m; 6 char ma[maxn][maxn]; 7 int dx[] = {-1, -1, -1, 0, 0, 1, 1, 1}; 8 int dy[] = {-1, 0, 1, -1, 1, -1, 0, 1}; 9 #define judge(x, y) 0 <= x && x < n && 0 <= y && y < m && ma[x][y] == ‘W‘ 10 void dfs(int x, int y) 11 { 12 ma[x][y] = ‘.‘;//这个处理是为了避免对该点重复操作。 13 for(int i = 0; i < 8; i++) 14 { 15 int fx = x + dx[i]; 16 int fy = y + dy[i]; 17 if(judge(fx, fy)) 18 dfs(fx, fy); 19 } 20 } 21 22 int main() 23 { 24 while(~scanf("%d%d", &n, &m)) 25 { 26 for(int i = 0; i < n; i++) 27 scanf("%s", ma[i]); 28 int ans = 0; 29 for(int i = 0;i < n; i++) 30 for(int j = 0; j < m; j++) 31 if(judge(i, j)) 32 dfs(i, j), ans++; 33 printf("%d\\n", ans); 34 } 35 return 0; 36 }
poj 1979 Red and Black(裸dfs)
题意:题意:给你一个row*col的矩阵,上面的‘#‘代表你不能走的地方,‘.‘表示你能走的地方,‘@‘表示你的起点,问你最多能走多少格。
1 #include <iostream> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 20 + 5; 5 6 int n, m, sx, sy, cnt; 7 int dx[] = {0, 0, 1, -1}; 8 int dy[] = {1, -1, 0, 0}; 9 char ma[maxn][maxn]; 10 #define judge(x, y) 0 <= x && x < n && 0 <= y && y < m 11 void dfs(int x, int y) 12 { 13 cnt++; 14 ma[x][y] = ‘#‘; 15 for(int i = 0; i < 4; i++) 16 { 17 int fx = x + dx[i],fy = y + dy[i]; 18 if(judge(fx, fy) && ma[fx][fy] == ‘.‘ ) 19 { 20 dfs(fx ,fy); 21 } 22 } 23 } 24 25 int main() 26 { 27 while(~scanf("%d%d", &m, &n) && m && n)//注意终止条件 28 { 29 for(int i = 0; i<n; i++) 30 { 31 for(int j = 0; j<m; j++) 32 { 33 cin >> ma[i][j]; 34 if(ma[i][j] == ‘@‘) 35 sx = i,sy = j; 36 } 37 } 38 cnt = 0; 39 dfs(sx, sy); 40 printf("%d\\n", cnt); 41 } 42 43 return 0; 44 }
poj 3009 Curling 2.0(dfs)
题意:给定一个m*n的网格,在这些网格上一些地方有障碍物,给定起点与终点的位置,当石头从起点开始走,撞上障碍才会转弯,否则会一直沿着来时的方向继续前进。撞到障碍后停在障碍前的位置,障碍消失。然后石头可以选择四个方向(相邻处无障碍的方向)前进,问至少需要停多少次才能从起点到达终点。不能到达或者多余10步后游戏失败。如果能到达输出最少的步数,否则输出-1.
思路:
在写dfs的时候,经常先写剪枝,再写终止条件(毕竟是递归,递归的终止是很重要的),再写一系列的搜索操作。首先我们发现这道题不一样的地方,在于每次移动的距离是不定的,所以我们采用一个while循环来进行不断的移动。这道题目,很关键一个地方是学会思考,怎么去思考,要从上往下思考,这道题的思路就非常清晰,而不会写的头晕脑转。
怎么去思考,你每移动到一个新格子,要整体的来看,一共有四种可能吧,0,1,2,3。那么就是四个分支。
if(==1) else if(==2) else if(==3) else
我们可以发现0和2是可以合并的,那么相当于这个if语句只有三块。我们 定好了大方向,再去完善每个分支。
1) 0或2:可以移动
2)1:这个时候碰到的墙可以撞开,那么就更改ma[fx][fy]=0,其次球是要停在墙之前的,也就是说dfs之前一个位置。完了以后要回溯ma[fx][fy]=1因为在其他的情况中它还是一堵墙,它没有被撞开,这个细节,叫做回溯,初学者经常会想不清楚,一定要想清楚。
3)3:到达终点,递归。
注意点:
首先第一个WA点:输入数据的时候,行和列的先后不一样了。
第二个点:很多题目中是n或m为0则终止输入,有的是n=0并且m=0才终止输入,一定要小心这个细节。
第三个点:从样例中要观察出一个题目没有提到的细节:就是如果要用球撞破墙,那么必须球的起始点和墙必须不是相邻的。
1 #include <cstdio> 2 #include <iostream> 3 4 using namespace std; 5 const int maxn = 20 + 5; 6 const int INF = 0x3f3f3f3f; 7 int n ,m, sx, sy, gx, gy, ans_cost; 8 9 int ma[maxn][maxn]; 10 int dx[] = {-1, 1, 0, 0}; 11 int dy[] = {0, 0, -1, 1}; 12 13 void dfs(int x, int y, int times) 14 { 15 if(times > 10) return ;//这是一步剪枝 16 if(ma[x][y] == 3 )//即到达终点的时候 递归终点 17 { 18 if(ans_cost == -1)//如果目前为止还没有找到过解,则更新解 19 ans_cost = times; 20 else if(ans_cost != -1 && times < ans_cost)//如果已经找到过解,那么需要判断是否已经找到的解和新的解,更新解为较小者 21 ans_cost = times; 22 return ; 23 } 24 25 for(int i = 0; i < 4; i++) 26 { 27 int fx = x + dx[i]; 28 int fy = y + dy[i]; 29 if(ma[fx][fy] == 1) continue;//这是墙,如果相邻的就是墙说明无法移动,跳过此数据 30 while(0<= fx && fx < n && 0 <= fy && fy < m) 31 { 32 if(ma[fx][fy] == 0 || ma[fx][fy] == 2) 33 { 34 fx += dx[i]; 35 fy += dy[i]; 36 } 37 else if(ma[fx][fy] == 1) 38 { 39 ma[fx][fy] = 0;//去除障碍物 40 dfs(fx - dx[i], fy - dy[i], times + 1); 41 ma[fx][fy] = 1;//回溯 42 break; 43 } 44 else if(ma[fx][fy] == 3) 45 { 46 dfs(fx, fy, times + 1); 47 break; 48 } 49 } 50 } 51 } 52 53 int main() 54 { 55 while(~scanf("%d%d", &m, &n)) 56 { 57 if(m == 0 && n == 0) break; 58 for(int i = 0; i < n; i++) 59 { 60 for(int j = 0; j < m; j++) 61 { 62 scanf("%d", &ma[i][j]); 63 if(ma[i][j] == 2) sx = i, sy = j; 64 } 65 66 } 67 ans_cost = -1; 68 dfs(sx, sy, 0); 69 printf("%d\\n", ans_cost); 70 } 71 return 0; 72 }
poj 3669 Meteor Shower(bfs)
题意:有个小文青去看流星雨,不料流星掉下来会砸毁上下左右中五个点。每个流星掉下的位置和时间都不同,求小文青能否活命,如果能活命,最短的逃跑时间是多少?
思路:
首先我们要明确,因为如果某一个点(x,y)被砸过,就从此以后再也不能走了。所以只要用ma[x][y]存下来最早砸过的时间就可以了。大于它的时间,相当于都可以舍弃了。
其次,要明确ma[x][y]==INF的点都是安全点。
首先是想到用预处理来使搜索的时候比较方便。不然搜索的时候还要再判断周围几个结点的流星雨的陨落的最早时间。
所以要先做一个预处理,见代码。每一个有流星雨的点,把它和它相邻的四个结点都放进for循环里一个个去一个更新最早流星雨陨落时间。
之后就是一个非常普通的bfs了。
注意点:
1)特殊数据 0 0 0 要特判。
2)不知道为什么,至少在poj上,这道题。用min去更新最小值的时候,会报TLE,不论是algorithm库里的min函数还是自己写的min函数,都会超时。然后用前者的比较的形式就能够AC。
3)bfs里面这段更新完距离之后,要记得把新结点进队。
4)一定要注意这里的pop不能直接在取出新结点之后就pop了。想想为什么。
1 #include <queue> 2 #include <cstdio> 3 using namespace std; 4 const int maxn = 300 + 5; 5 const int INF = 0x3f3f3f3f; 6 #define judge(x, y) 0 <= x && 0 <= y 7 8 int ma[maxn][maxn]; 9 int d[maxn][maxn]; 10 int dx[] = {0, 0, 1, -1, 0}; 11 int dy[] = {-1, 1, 0, 0, 0}; 12 13 struct Node 14 { 15 int x, y; 16 Node(int xx, int yy):x(xx), y(yy){} 17 }; 18 19 void bfs(int x, int y) 20 { 21 if(ma[x][y] == 0) {printf("-1\\n");return ;}//特殊情况的一个判断:(0,0)在0s的时候就有陨石陨落 22 queue<Node>que; 23 Node p(x, y); 24 d[x][y] = 0; 25 que.push(p); 26 27 while(!que.empty()) 28 { 29 Node newp = que.front(); 30 int px = newp.x; 31 int py = newp.y; 32 if(ma[px][py] == INF) break;//到达安全点,break 33 que.pop();//***要放在break之后 34 for(int i = 0; i < 4; i++) 35 { 36 int fx = px + dx[i]; 37 int fy = py + dy[i]; 38 if(judge(fx, fy)) 39 { 40 if(ma[fx][fy] > d[newp.x][newp.y] + 1 && d[fx][fy] > d[newp.x][newp.y] + 1) 41 { 42 d[fx][fy] = d[newp.x][newp.y] + 1; 43 que.push(Node(fx, fy)); 44 } 45 } 46 } 47 48 } 49 if(que.empty())//如果队空了 50 { 51 printf("-1\\n"); 52 } 53 else 54 { 55 Node newp = que.front(); 56 printf("%d\\n", d[newp.x][newp.y]); 57 } 58 } 59 60 int main() 61 { 62 int M, x, y, t; 63 while(~scanf("%d", &M)) 64 { 65 for(int i = 0; i < maxn; i++) 66 { 67 for(int j = 0; j < maxn; j++) 68 { 69 ma[i][j] = INF;//记录每个点的流星雨的最早陨落时间 70 d[i][j] = INF;//记录每个点的最少时间 71 } 72 } 73 for(int i = 0; i < M; i++) 74 { 75 scanf("%d%d%d", &x, &y, &t); 76 for(int j = 0; j < 5; j++) 77 { 78 int fx= x + dx[j]; 79 int fy = y + dy[j]; 80 if(judge(fx, fy) && ma[fx][fy] > t) 81 ma[fx][fy] = t; 82 } 83 } 84 bfs(0, 0); 85 } 86 return 0; 87 }
poj 2718 Smallest Difference(穷竭搜索,枚举)
题意:给出最多10个数字,将它们划分为两个整数,求差异值最小的值(除非只有一位数,否则不允许出现先导0)
思路:
分析题目给的数据范围,可以发现,它可以用枚举法。怎么分析呢。
首先最多10个数。10个数有10!,三十六万多种排列而已。也就说撑死不到四十万。而我们要求最小的差,那么说明n是偶数的时候,一定是两个位数相同的数作差,如果是奇数,说明是一个n/2和n/2+1的两个数作差。等价于它的一个排列的第一个数到p/2个数,后面组成一个数,两个数的差。枚举的话也就四十万种不到,是可以解决的。
因为首位数不能为0,所以有一个剪枝。
再回过头来考虑一个特殊情况,就是n=2的时候,应该要特殊处理。
再注意一个输入的方式。
1 #include <iostream> 2 #include <cstdio> 3 #include <algorithm> 4 using namespace std; 5 int a[15]; 6 int main() 7 { 8 int T; 9 scanf("%d", &T); 10 int t; 11 while(T--) 12 { 13 int p = 0; 14 char ch; 15 int ans = 0x3f3f3f3f; 16 while(~scanf("%d%c", &a[p++], &ch)) 17 if(ch == ‘\\n‘) break; 18 if(p == 2) 19 { 20 cout << abs(a[0]-a[1] )<< endl; 21 continue; 22 } 23 24 do{ 25 if(a[0] == 0 || a[p / 2] == 0) continue;//这种情况明显不可能 26 int A = 0, B = 0; 27 for(int i = 0; i < p / 2; i++) 28 A = A * 10 + a[i]; 29 for(int i = p / 2; i < p; i++) 30 B = B * 10 + a[i]; 31 int temp = abs(A - B); 32 if(temp < ans) ans = temp; 33 }while(next_permutation(a, a + p)); 34 cout<< ans <<endl; 35 } 36 return 0; 37 }
poj 3187 Backward Digit Sums(暴搜)
题意:给出杨辉三角的顶点值sum和底边数的个数n,求出底边各个数的值。
思路:
要对数据范围足够敏感,n这么小,直接暴搜就行了 。暴搜全排列,模拟杨辉三角加法,看答案是否匹配。
1 #include <cstdio> 2 #include <algorithm> 3 using namespace std; 4 5 int a[15], b[15]; 6 7 int main() 8 { 9 int n, sum; 10 while(~scanf("%d%d", &n, &sum)) 11 { 12 for(int i= 1; i <= n; i++) 13 { 14 a[i] = i; 15 } 16 do{ 17 int len = n; 18 for(int i = 1; i <= n; i++) 19 b[i] = a[i]; 20 while(len > 1) 21 { 22 for(int i = 1; i < len; i++) 23 { 24 b[i] = b[i] + b[i + 1]; 25 } 26 len --; 27 } 28 if(b[1] == sum) 29 { 30 for(int i = 1; i <= n; i++) 31 { 32 printf("%d%c", a[i], i == n ? ‘\\n‘ : ‘ ‘); 33 } 34 break; 35 } 36 }while(next_permutation(a + 1, a + 1 + n)); 37 } 38 return 0; 39 }
poj 3050 Hopscotch(暴力dfs+set/数组)
题意:
1.5*5的方阵中,先随意挑一格,记住这个格子的数字
2.可以上下左右走,走5次,每走一次记录下所走格子的数字
3.经过以上步奏,把总共6个数字连起来,形成一串数字。求总共可以形成多少种不同的数字串
思路:
思考一下状态数,一共也没多少种,直接暴力,暴力出真知。这道题贴两个代码,区别在一个是用了数组,一个是用set,一个牺牲了时间,一个牺牲了空间.
1 #include <iostream> 2 #include <cstring> 3 using namespace std; 4 int ma[25][25]; 5 int Hash[1000000]; 6 int cnt; 7 int dx[] = {0, 0, 1, -1}; 8 int dy[] = {1, -1, 0, 0}; 9 void solve(int x, int y, int sum, int level) 10 { 11 if(level == 6) 12 { 13 if(Hash[sum] == 0) 14 cnt++, Hash[sum] = 1; 15 return ; 16 } 17 for(int i = 0; i < 4; i++) 18 { 19 int fx = x + dx[i];int fy = y + dy[i]; 20 if(0 <= fx && fx < 5 && 0 <= fy && fy < 5) 21 { 22 solve(fx, fy, sum * 10 + ma[fx][fy], level + 1); 23 } 24 } 25 26 } 27 int main() 28 { 29 30 for(int i = 0; i < 5; i++) 31 for(int j = 0; j < 5; j++) 32 cin >> ma[i][j]; 33 34 cnt = 0; 35 memset(Hash, 0, sizeof(Hash)); 36 for(int i = 0; i < 5; i++) 37 { 38 for(int j = 0; j < 5; j++) 39 { 40 solve(i, j, ma[i][j], 1); 41 } 42 } 43 cout << cnt << endl; 44 return 0; 45 }
1 #include <iostream> 2 #include <set> 3 #include <cstdio> 4 using namespace std; 5 6 int temp; 7 set <int> s; 8 int dx[] = {0, 0, -1, 1}; 9 int dy[] = {1, -1, 0, 0}; 10 int ma[10][10]; 11 #define judge(x, y) 0 <= x && x < 5 && 0 <= y && y < 5 12 void solve(int x, int y, int n, int sum) 13 { 14 if(n == 6) 15 { 16 s.insert(sum); 17 return ; 18 } 19 sum = sum * 10 + ma[x][y]; 20 for(int i = 0; i < 4; i++) 21 { 22 int fx = x + dx[i]; 23 int fy = y + dy[i]; 24 if(judge(fx, fy)) 25 { 26 solve(fx, fy, n + 1, sum); 27 } 28 } 29 } 30 int main() 31 { 32 33 for(int i = 0; i < 5; i++) 34 for(int j = 0; j < 5; j++) 35 cin >> ma[i][j]; 36 37 for(int i = 0; i < 5; i++) 38 { 39 for(int j = 0; j < 5; j++) 40 { 41 solve(i, j, 0, 0); 42 } 43 } 44 cout << s.size() << endl; 45 return 0; 46 }
以上是关于《挑战程序设计竞赛》第二章的主要内容,如果未能解决你的问题,请参考以下文章
挑战程序设计竞赛(算法和数据结构)——5.4 散列法习题Java代码实现
挑战程序设计竞赛(算法和数据结构)——4.6 数据结构的应用——计算面积Java代码实现