现在的位置: 首页 > 综合 > 正文

搜索总结

2012年05月18日 ⁄ 综合 ⁄ 共 4548字 ⁄ 字号 评论关闭

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 <

抱歉!评论已关闭.