搜索总结
1.Sticks
对于WA,TLE很多次的人说,这道题绝对经典!
我也是看了这位仁兄的代码才AC = =! , http://www.cnblogs.com/lotus3x/archive/2008/07/25/1251552.html
void BackTrack(int i , int c , int cnt)
{
int j , pre;
if(cnt == M)
{
nFlag = 1;
return;
}
if(c == L)
{
BackTrack(0 , 0 , cnt + 1);
return ;
}
for(j = i , pre = -1 ; j < n ; j++)
{
if(b[j] == 0 && pre != a[j] && c + a[j] <= L)
{
pre = a[j];
b[j] = 1;
BackTrack(j + 1 , c + a[j] , cnt);
b[j] = 0;
if(i == 0 || nFlag == 1) return;
}
}
}
剪枝1:因为如果这些sticks能够分成M段,那么每个stick一定都能找到属于自己的段(我感觉这是最重要的剪枝,之前一直TLE就是因为这里,程序没有及时的返回,一直在计算“不可能的情况”);
剪枝2:充分利用a[]已排序(从大到小).所以是 BackTrack(j + 1,...) 而不是 BackTrack(i + 1,...);
剪枝3:主要是针对6 6 3 2 这种类似序列(规定L = 10),若已经选取第一个6和3无法组合成一段(6+3+2>10),那么回溯的时候就没有必要在尝试第二个6了。即pre != a[j];
Square也是Sticks的简化版,明白了这道题,Square也就AC了。
总结:
该类问题可以看做是:把若干个元素分成几个集合,使得每个集合的和相等。
算法就是:排序(从大到小) + DFS + 剪枝
2.Tempter of the Bone (参考:http://blog.csdn.net/lovelyloulou/archive/2009/06/07/4248584.aspx)
这道题是个典型的迷宫搜索问题,唯一的限制就是:不能走回头路,而且要在指定的步数到达终点。
很自然,拿到这题我就用DFS写了一大通,结果超时,悲剧!然后我又尝试了剪枝(计算路程下界),结果还是超时,Oh my lady gaga!
最后看了别人的解题报告才明白,原来这题的关键是“奇偶剪枝”。何谓“奇偶剪枝”?
可以把map看成这样:
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
1 0 1 0 1 0
0 1 0 1 0 1
从0->1 或 1->0 必然是奇数步
从0->0 或 1->1 必然是偶数步
所以当遇到从0走向0但是要求时间是奇数的,或者从1走向0但是要求时间是偶数的,都可以直接判断不可达!
1.剪枝1: 计算路程下界,比如我们当前在位置(i,j),那么从这个位置到终点的最小步数就是 abs(end_x - i) + abs(end_y - j),如果这个这个步数 + cnt(当前已经走的步数) > T ,那么就没有必要在位置(i,j)扩展下去了;
2.剪枝2:奇偶剪枝;
3.剪枝3:在输入时统计可通过的方格数,如果方格数 < T,就可以直接输出结果;
#include <stdio.h>
#include <string.h>
#include <math.h>
int N , M , T , nFlag , cnt;
int start_x , start_y , end_x , end_y;
char maze[7][7];
int direct[][2] = {
{-1 , 0}, //up
{0 , 1} , //right
{1 , 0} , //down
{0 , -1}, //left
};
int step(int i , int j)
{
return abs(end_x - i) + abs(end_y - j);
}
void DFS(int i , int j)
{
int x , y , k , nTemp;
if(i == end_x && j == end_y && cnt == T) //到达终点
{
nFlag = 1;
return;
}
nTemp = T - cnt - step(i,j);
if(nTemp < 0 || nTemp & 1) return; //剪枝1,2(下界剪枝,奇偶剪枝)
for(k = 0 ; k < 4 ; k++)
{
x = i + direct[k][0];
y = j + direct[k][1];
if((x >= 0 && x < N && y >=0 && y < M) && maze[x][y] != 'X')
{
cnt++; maze[x][y] = 'X';
DFS(x , y);
if(nFlag == 1) return;
cnt--; maze[x][y] = '.';
}
}
}
int main(void)
{
int i , j , wall;
char c;
while(scanf("%d%d%d", &N, &M, &T) && N + M + T)
{
wall = cnt = nFlag = 0;
for(i = 0 ; i < N ; i++)
{
for(j = 0 ; j < M ; j++)
{
scanf(" %c", &c);
maze[i][j] = c;
if(c == 'S') //记录起始位置
{
wall++;
start_x = i;
start_y = j;
}
else if(c == 'D') //记录终点位置
{
end_x = i;
end_y = j;
}
else if(c == 'X') //记录不能走的格子数目(一开始因为没有检测这个而超时,= =!)
{
wall++;
}
}
}
maze[start_x][start_y] = 'X';
if(N * M - wall >= T) DFS(start_x , start_y);
printf(nFlag ? "YES\n" : "NO\n");
}
return 0 ;
}
总结:奇偶剪枝。
4.A Knight's Journey
这题就是纯DFS,比较容易(要按字典序输出,因此探索的方向有考究)
#include <stdio.h>
#include <string.h>
#define N 27
struct
{
int x , y;
}path[N];
int n , m , nFlag;
int c[N][N];
int dir[][2]={
{-2,-1},
{-2,1 },
{-1,-2},
{-1,2 },
{1,-2 },
{1,2 },
{2,-1 },
{2,1 }
};
void SearchPath(int i , int j , int cnt)
{
int x , y , k;
if(cnt == n * m)
{
nFlag = 1;
return;
}
for(k = 0 ; k < 8 ; k++)
{
x = i + dir[k][0];
y = j + dir[k][1];
if((x > 0 && x <= n && y > 0 && y <= m) && !c[x][y])
{
c[x][y] = 1;
path[cnt + 1].x = x;
path[cnt + 1].y = y;
SearchPath(x , y , cnt + 1);
if(nFlag) return;
c[x][y] = 0;
}
}
}
int main(void)
{
int z , k , i , j;
k = 1;
scanf("%d", &z);
while(z-- > 0)
{
scanf("%d%d", &m, &n); //用memset耗时
for(i = 1 ; i <= n ; i++)
for(j = 1 ; j <= m ; j++)
c[i][j] = 0;
nFlag = 0;
c[1][1] = path[1].x = path[1].y = 1;
SearchPath(1 , 1 , 1);
printf("Scenario #%d:\n", k); k++;
if(nFlag)
{
for(i = 1 ; i <= n * m ; i++)
printf("%c%d", path[i].x + 64 , path[i].y);
printf("\n");
}
else
{
printf("impossible\n");
}
if(z != 0) printf("\n");
}
return 0;
}
5.Oil Deposits , Red and Black
这两题都是利用图的深度优先遍历。因为一次DFS的结果就是一个连通子图, Oil Deposits这题就是想求需要几次DFS才能遍历所有的存储油点(连通子图个数);而Red and Black这题就是求,指定起点所在连通子图中的结点个数(不要被题目中描述可以回走迷惑!)
//Oil Deposits
#include <stdio.h>
#include <string.h>
struct Elem
{
int i;
int j;
}s[10001];
int N , M , nFlag , cnt , next;
char m[101][101];
int direct[][2] = {
{-1 , 0}, //up
{-1 , 1},
{0 , 1} , //right
{1 , 1} ,
{1 , 0} , //down
{1 , -1},
{0 , -1}, //left
{-1 ,-1}
};
void DFS(int i , int j)
{
int x , y , k;
if(cnt == 0)
{
nFlag = 1;
return;
}
for(k = 0 ; k < 8 ; k++)
{
x = i + direct[k][0];
y = j + direct[k][1];
if((x >= 0 && x < N && y >=0 && y < M) && m[x][y] != '*')
{
cnt--; m[x][y] = '*';
DFS(x , y);
if(nFlag == 1) return;
}
}
}
int main(void)
{
int i , j , t , k;
char c;
while(scanf("%d%d", &N, &M) && N + M)
{
t = next = nFlag = cnt = 0;
for(i = 0 ; i < N ; i++)
{
for(j = 0 ; j < M ; j++)
{
scanf(" %c", &c);
m[i][j] = c;
if(c == '@') //记录DFS所以起点
{
s[next].i = i;
s[next].j = j;
next++;
cnt++;
}
}
}
for(k = 0 ; k < next ; k++)
{
if(!nFlag)
{
i = s[k].i; j = s[k].j;
if(m[i][j] == '@')
{
t++; cnt--;
m[i][j] = '*';
DFS(i , j);
}
}
}
printf("%d\n", t);
}
return 0 ;
}
//Red and Black#include <stdio.h>
#include <math.h>
#define N 21
char maze[N][N];
int n , m , cnt;
int direct[][2] = {
{-1 , 0}, //up
{0 , 1} , //right
{1 , 0} , //down
{0 , -1}, //left
};
//其实就是图的遍历(指定起点)
void DFS(int i , int j)
{
int k , x , y;
for(k = 0 ; k < 4 ; k++)
{
x = i + direct[k][0];
y = j + direct[k][1];
if((x >= 0 && x < n && y >= 0 && y < m) && maze[x][y] == '.')
{
maze[x][y] = '#'; cnt++;
DFS(x,y);
}
}
}
int main(void)
{
int i , j , start_x , start_y;
char c;
while(scanf("%d%d", &m,&n) && n + m)
{
for(i = 0 ; i < n ; i++)
for(j = 0 ; j < m ; j++)
{
scanf(" %c",&c);
maze[i][j] = c;
if(c == '@')
{
start_x = i;
start_y = j;
}
}
cnt = 1;
DFS(start_x , start_y);
printf("%d\n", cnt);
}
return 0 ;
}
6.Fire Net
这题乍看和n皇后很像,区别就是在一定的条件下,棋子可以同行同列(中间有方块间隔)因此横向DFS即可。
(关键代码就是 x = k / n ; y = k % n)
#include <stdio.h>
int m[5][5] , n , size , best;
int CanPut(int x ,int y)
{
int i , j;
for(i = x - 1 ; i >= 0 ; i--)
{
if(m[i][y] == 'A') return 0;
if(m[i][y] == 'X') break;
}
for(j = y - 1 ; j >= 0 ; j--)
{
if(m[x][j] == 'A') return 0;
if(m[x][j] == 'X') break;
}
return 1;
}
void DFS(int k , int cnt)
{
int x , y;
if(k == size)
{
if(cnt > best) best = cnt;
return ;
}
x = k / n;
y = k % n;
if(m[x][y] == '.' && CanPut(x,y))
{
m[x][y] = 'A'; //标记走过
DFS(k + 1 , cnt + 1);
m[x][y] = '.';
}
if((n - x) * 2 + cnt >= best) DFS(k + 1 , cnt); //剪枝,(n - x) * 2 + cnt 上届
}
int main(void)
{
int i , j;
while(scanf("%d", &n) && n)
{
for(i = 0 ; i < n ; i++)
for(j = 0 ; j < n ; j++)
scanf(" %c", &m[i][j]);
best = 0; size = n * n;
DFS(0 , 0);
printf("%d\n",best);
}
return 0;
}
7.Anagrams by Stack
这题实际上就是模拟栈的进出所有情况(DFS + 回溯)类似一棵子集树,只要搞清楚一下几点,程序自然就可以AC:
1).何时可以入栈,当p1没有指向s1的末尾时可以继续入栈,否则不能。即p1 < Len;
2).何时可以出栈,第一,栈不为空,第二,出栈的字符要和目标字符串匹配。即top > 0 && stack[top-1] == s2[p2]
最后要注意的一点就是,回溯的时候不仅要恢复栈顶指针,栈中元素也要恢复(一开始就是忘记这点,使得结果总是不对!)
#include <stdio.h>
#include <string.h>
#define N 256
char s1[N] , s2[N] , stack[N] , x[N];
int Len , top , p1 , p2;
void DFS(int i)
{
int j;
char c;
if(i == Len * 2)
{
for(j = 0 ; j < i ; j++) printf("%c ",x[j]);
printf("\n");
return ;
}
//push
if(p1 < Len)
{
stack[top] = s1[p1];
p1++; top++;
x[i] = 'i';
DFS(i + 1);
p1--; top--;
}
//pop
if(top > 0 && stack[top-1] == s2[p2])
{
top--; p2++;
x[i] = 'o';
DFS(i + 1);
top++; p2--;
stack[top-1] = s2[p2]; //恢复栈
}
}
int main(void)
{
int i , j;
/*p1是供s1使用的下标,p2是供s2使用的下标,top是stack的栈顶指针*/
while(scanf("%s%s", s1 , s2) != EOF)
{
Len = strlen(s1);
if(Len != strlen(s2))
puts("[\n]");
else
{
printf("[\n");
top = p1 = p2 = 0;
DFS(0);
puts("]");
}
}
return 0;
}