ACM/ICPC 之 DFS范例(ZOJ2412-ZOJ1008)
通过几道例题简单阐述一下DFS的相关题型
ZOJ2412-Farm Irrigation
直观的DFS题型,稍加变化,记录好四个方向上的通路就能够做出来
题目和接水管类似,问最少要灌溉几次,即求解最少有多少个连通子图。
1 //和接水管游戏类似,将相应水管通路标记清晰即可 2 //Time:0Ms Memory:270K 3 #include<iostream> 4 #include<cstring> 5 #include<cstdio> 6 using namespace std; 7 #define MAX 55 8 #define OPPOSITE(x) ((x + 2) % 4) //x的相反位置 9 #define MAP(x,y) (map[x][y] - 'A') //水管序列 10 int row, col; 11 char map[MAX][MAX]; 12 bool v[MAX][MAX]; 13 int mov[4][2] = { {0, 1}, {1, 0}, {0, -1}, {-1, 0} }; //东南西北 14 bool face[4][11] = { //face[i][j] : 在i方向上第j个水管是否有通路 15 { 0,1,0,1,0,1,1,0,1,1,1 }, //东 16 { 0,0,1,1,1,0,0,1,1,1,1 }, //南 17 { 1,0,1,0,0,1,1,1,1,0,1 }, //西 18 { 1,1,0,0,1,0,1,1,0,1,1 } //北 19 }; 20 void dfs(int x,int y) 21 { 22 v[x][y] = true; 23 for (int i = 0; i < 4; i++) 24 { 25 int tx = x + mov[i][0]; 26 int ty = y + mov[i][1]; 27 if (face[i][MAP(x,y)] && tx >= 0 && tx < row && ty >= 0 && ty < col) //该水管相应对接方向有通路 28 { 29 if (!v[tx][ty] && face[OPPOSITE(i)][MAP(tx,ty)]) //对接水管相应方向有通路 30 dfs(tx, ty); 31 } 32 } 33 } 34 int main() 35 { 36 while (scanf("%d%d", &row, &col), row != -1 && col != -1) 37 { 38 memset(v, false, sizeof(v)); 39 for (int i = 0; i < row; i++) 40 scanf("%s", map[i]); 41 int times = 0; 42 for (int i = 0; i < row; i++) 43 for (int j = 0; j < col; j++) 44 { 45 if (!v[i][j]) { 46 times++; 47 dfs(i, j); 48 } 49 } 50 printf("%d\n", times); 51 } 52 return 0; 53 }
ZOJ1008-Gnome Tetravex
看起来不像个搜索题,初看可能会以为需要枚举之类的,但是题中方块的各状态需要记录,在匹配失败时需要回退,因此是一道DFS题型。
大致的解题思路就是先枚举0行0列的方块,再依据此方块固定下一个方块,以此类推,出现不能匹配时回退。
虽然规模最大只有5,但是总方块数25个在DFS中也不可小觑,如果用纯DFS来做,时间度最坏可以达到O((n^2)!),因此剪枝或其他优化是必须的(只要数据不够水),我在超时后看了很多博客的解题报告(想不到了= =),就该题数据而言最好的优化方法是去重,即将相同方块合在一起,以减少DFS的分支数,但是我总觉得这么做挺奇怪的。。。虽然在大数据下,此题去重很有效果(数字在0-9,因此方块重复概率 < 1/2500),但是此题拿小数据故意出多组重复方块,不得不让人怀疑其心不善...
另外的剪枝方法,也有很多比较有效,但对此题没有太大帮助。
例如:记录各方向上各数字的个数,在匹配时动态增删,如果已经固定的方块需要的对应数字的数量缺失,那么就可以直接回退,剪枝效果在少量随机数据情况下比较好。
1 //去重就不会超时了,但这个优化实在是...让人感到很意外 2 //Time:2100ms Memory:272K 3 #include<iostream> 4 #include<cstring> 5 #include<cstdio> 6 using namespace std; 7 8 #define MAX 28 9 10 int n, m; 11 int sq[MAX][4]; //上右下左 12 int board[MAX]; //棋盘上对应位置的square 13 int v[MAX]; //记录同类sq未被使用的数量 14 15 bool dfs(int num) 16 { 17 if (num == n*n) 18 return true; 19 //找出可以充当第num个方块的i方块 20 for (int i = 0; i < m; i++) 21 { 22 if (!v[i]) continue; //没有未固定方块 23 if (num % n && sq[i][3] != sq[board[num - 1]][1]) continue; //非第一列+不匹配 24 if (num / n && sq[i][0] != sq[board[num - n]][2]) continue; //非第一行+不匹配 25 26 v[i]--; 27 board[num] = i; 28 if (dfs(num + 1)) return true; 29 else v[i] ++; 30 } 31 return false; 32 } 33 34 int main() 35 { 36 int t = 0; 37 while (scanf("%d", &n), n) 38 { 39 if (t) printf("\n"); 40 41 memset(v, 0, sizeof(v)); 42 memset(board, 0, sizeof(board)); 43 m = 0; 44 for (int i = 0; i < n*n; i++) 45 { 46 int u, r, d, l; 47 scanf("%d%d%d%d", &u, &r, &d, &l); 48 //查重 49 bool flag = false; 50 for (int j = 0; j < m; j++) 51 { 52 if (u == sq[j][0] && r == sq[j][1] && d == sq[j][2] && l == sq[j][3]) 53 { 54 v[j]++; 55 flag = true; 56 break; 57 } 58 } 59 if (!flag) { 60 sq[m][0] = u; sq[m][1] = r; sq[m][2] = d; sq[m][3] = l; 61 v[m++] ++; 62 } 63 } 64 65 if (dfs(0)) printf("Game %d: Possible\n", ++t); 66 else printf("Game %d: Impossible\n", ++t); 67 } 68 69 return 0; 70 }
他坐在湖边,望向天空,她坐在对岸,盯着湖面