搜索总结

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
->11->0 必然是奇数步
从0
->01->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;
}

 

 

posted on 2010-04-24 14:14  DiaoCow  阅读(404)  评论(0编辑  收藏  举报

导航