参考:http://exp-blog.com/
https://blog.csdn.net/consciousman/article/details/54613292
POJ2488 – A Knight’s Journey【骑士游历】
大致题意
给出一个国际棋盘的大小,判断马能否不重复的走过所有格,并记录下其中按字典序排列的第一种路径。
经典的“骑士游历”问题,DFS水题一道
解题思路 难度不大,但要注意的地方有3点: 1、 题目要求以”lexicographically”方式输出,也就是字典序…要以字典序输出路径,那么搜索的方向(我的程序是path()函数)就要以特殊的顺序排列了…这样只要每次从dfs(A,1)开始搜索,第一个成功遍历的路径一定是以字典序排列… 下图是搜索的次序,马的位置为当前位置,序号格为测试下一步的位置的测试先后顺序 按这个顺序测试,那么第一次成功周游的顺序就是字典序 2、国际象棋的棋盘,行为数字p;列为字母q 3、网上有同学说 这道题最后一组数据后是有空行的会PE…,我测试过,不会的,能AC
//Memory Time
//240K 32MS
#include
using namespace std;
typedef class
{
public:
int row;
char col;
}location;
int p,q; //chess size = p*q
//数字是行p,字母是列q
bool chess['Z'+1][27];
int x,y; //返回值
void path(int i,int j,int num) //ij为骑士当前在棋盘的位置
{ //num为骑士即将要跳到的位置序号
switch(num)
{
case 1: {x=i-1; y=j-2; break;} //注意这个尝试跳的顺序不能错
case 2: {x=i+1; y=j-2; break;} //因为题目要求是字典序lexicographically输出
case 3: {x=i-2; y=j-1; break;} //这个顺序错了,必定WA
case 4: {x=i+2; y=j-1; break;}
case 5: {x=i-2; y=j+1; break;}
case 6: {x=i+2; y=j+1; break;}
case 7: {x=i-1; y=j+2; break;}
case 8: {x=i+1; y=j+2; break;}
}
return;
}
bool DFS(location* way,int i,int j,int step)
{
chess[i][j]=true;
way[step].row=i;
way[step].col=j;
if(step==way[0].row)
return true;
for(int k=1;k<=8;k++) //骑士从当前位置尝试跳到其他位置
{
path(i,j,k);
int ii=x,jj=y;
if(!chess[ii][jj] && ii>=1 && ii<=p && jj>='A' && jj<='A'+q-1)
if(DFS(way,ii,jj,step+1))
return true;
}
chess[i][j]=false; //能执行到这步,说明前面跳的8步都不符合要求
return false; //即当前位置是错误位置,擦除记录返回上一步
}
int main(void)
{
int test;
cin>>test;
int t=1;
while(t<=test)
{
/*Initial*/
memset(chess,false,sizeof(chess));
cin>>p>>q;
if(p==1 && q==1) //范围缩窄,不要也能AC
{
cout<<"Scenario #"<26 || p>=9 || q>=9 || p<=2 || q<=2) //范围缩窄,不要也能AC
{
cout<<"Scenario #"<
2、poj3083
题目大意
给定一个迷宫,S是起点,E是终点,”#”是墙不可走,”.”可以走
先输出左转优先时,从S到E的步数
再输出右转优先时,从S到E的步数
最后输出S到E的最短步数
W为宽,列数 H为高,行数(3 <= w, h <= 40)
一个“S”和一个“E”将出现在迷宫中,它们将始终位于迷宫的一个边缘,而不是一个角落。迷宫将完全由墙壁('#')包围,唯一的开口是'S'和'E'。“S”和“E”之间还将至少隔一堵墙(“#”)。
Sample Input
2
8 8
########
#......#
#.####.#
#.####.#
#.####.#
#.####.#
#...#..#
#S#E####
9 5
#########
#.#.#.#.#
S.......E
#.#.#.#.#
#########
Sample Output
37 5 5
17 17 9
解题思路
DFS和BFS的综合题水题,难度不大,但是写代码时要注意几方面:
1、 左转、右转优先搜索时必须标记当前位置时的方向,我定义的方向是
最初的方向由起点S确定,而下一步的方向则由前一步的走向决定
例如 左边优先搜索:
当前位置的方向指向 1(向左),(这同时说明前一步是在第“3”的位置走过来的)
那么走下一步时,就要根据2103的顺序,先逐格确定当前位置周边的四格是否可行
若第一次确认2可行,就走到2,在位置2时的方向为2(向下)
若2不可行,则再确定1,若1可行,就走到1,在位置1时的方向为1(向左)
若1也不可行,则再确定0,若0可行,就走到0,在位置0时的方向为0(向上)
若0也不可行,说明进入了迷宫的死胡同,要从原路返回,走回3
右边优先搜索也同理。
根据我定义的方向,设当前位置为d,那么
左转,用数学式子表达就是 d=(d+1)%4
右转,用数学式子表达就是 d=(d+3)%4
我比较懒,在我的程序中,DFS和BFS都用了多入口的做法,有兴趣的同学可以利用我给出的这两个式子对代码进行优化。
这里有一点必须要注意的:
左边、右边优先搜索都不是找最短路,因此走过的路可以再走,无需标记走过的格
2、寻找最短路只能用BFS
因此在做第3问时别傻乎乎的又用DFS,DFS对于样例的输入确实和BFS得到的结果一样的,别以为样例PASS就提交了。。。所以我就说样例没代表性,学会测试数据很重要= =
注意有一点:
要求E的最短路,必须把迷宫模拟为树,S为根,找到E所在的层(树深),该层就是S到E的最短路,处理技巧就是在BFS时,令queue[tail]的depth等于对应的queue[head]的depth+1,详细见我的程序
把循环的次数作为深度就铁定错的
//Memory Time
// 212K 0MS
#include
using namespace std;
typedef class
{
public:
int r,c;
int depth;
}SE;
SE s,e; //起止点
int Lstep; //左边优先搜索 时从S到E的总步数
int Rstep; //右边优先搜索 时从S到E的总步数
int shortstep; //S到E的最少总步数
bool maze[41][41]; //记录迷宫的“可行域”与“墙”
void DFS_LF(int i,int j,int d) //左边优先搜索,i,j为当前点坐标,d为当前位置方向
{
Lstep++;
if(i==e.r && j==e.c)
return;
switch(d)
{
case 0:
{
if(maze[i][j-1])
DFS_LF(i,j-1,1);
else if(maze[i-1][j])
DFS_LF(i-1,j,0);
else if(maze[i][j+1])
DFS_LF(i,j+1,3);
else if(maze[i+1][j])
DFS_LF(i+1,j,2);
break;
}
case 1:
{
if(maze[i+1][j])
DFS_LF(i+1,j,2);
else if(maze[i][j-1])
DFS_LF(i,j-1,1);
else if(maze[i-1][j])
DFS_LF(i-1,j,0);
else if(maze[i][j+1])
DFS_LF(i,j+1,3);
break;
}
case 2:
{
if(maze[i][j+1])
DFS_LF(i,j+1,3);
else if(maze[i+1][j])
DFS_LF(i+1,j,2);
else if(maze[i][j-1])
DFS_LF(i,j-1,1);
else if(maze[i-1][j])
DFS_LF(i-1,j,0);
break;
}
case 3:
{
if(maze[i-1][j])
DFS_LF(i-1,j,0);
else if(maze[i][j+1])
DFS_LF(i,j+1,3);
else if(maze[i+1][j])
DFS_LF(i+1,j,2);
else if(maze[i][j-1])
DFS_LF(i,j-1,1);
break;
}
}
return;
}
void DFS_RF(int i,int j,int d) //右边优先搜索,i,j为当前点坐标,d为当前位置方向
{
Rstep++;
if(i==e.r && j==e.c)
return;
switch(d)
{
case 0:
{
if(maze[i][j+1])
DFS_RF(i,j+1,3);
else if(maze[i-1][j])
DFS_RF(i-1,j,0);
else if(maze[i][j-1])
DFS_RF(i,j-1,1);
else if(maze[i+1][j])
DFS_RF(i+1,j,2);
break;
}
case 1:
{
if(maze[i-1][j])
DFS_RF(i-1,j,0);
else if(maze[i][j-1])
DFS_RF(i,j-1,1);
else if(maze[i+1][j])
DFS_RF(i+1,j,2);
else if(maze[i][j+1])
DFS_RF(i,j+1,3);
break;
}
case 2:
{
if(maze[i][j-1])
DFS_RF(i,j-1,1);
else if(maze[i+1][j])
DFS_RF(i+1,j,2);
else if(maze[i][j+1])
DFS_RF(i,j+1,3);
else if(maze[i-1][j])
DFS_RF(i-1,j,0);
break;
}
case 3:
{
if(maze[i+1][j])
DFS_RF(i+1,j,2);
else if(maze[i][j+1])
DFS_RF(i,j+1,3);
else if(maze[i-1][j])
DFS_RF(i-1,j,0);
else if(maze[i][j-1])
DFS_RF(i,j-1,1);
break;
}
}
return;
}
void BFS_MSS(int i,int j) //最短路搜索
{
bool vist[41][41]={false};
SE queue[1600];
int head,tail;
queue[head=0].r=i;
queue[tail=0].c=j;//queue[0]表示起点的坐标
queue[tail++].depth=1; //当前树深标记,这是寻找最短路的关键点
vist[i][j]=true;
while(head>test;
while(test--)
{
int direction; //起点S的初始方向
int w,h; //size of maze
cin>>w>>h;
/*Initial*/
Lstep=1;
Rstep=1;
memset(maze,false,sizeof(maze));
/*Structure the Maze*/
for(i=1;i<=h;i++)
for(j=1;j<=w;j++)
{
char temp;
cin>>temp;
if(temp=='.')//可以走的路
maze[i][j]=true;
if(temp=='S')//起点
{
maze[i][j]=true;
s.r=i;
s.c=j;
if(i==h)//起点在最后一行
direction=0;
else if(j==w)//起点在最后一列
direction=1;
else if(i==1)//起点在第一行
direction=2;
else if(j==1)//起点在第一列
direction=3;
}
if(temp=='E')//终点
{
maze[i][j]=true;
e.r=i;
e.c=j;
}
}
/*Left First Search*/
switch(direction)
{
case 0: {DFS_LF(s.r-1,s.c,0); break;}//左边优先搜索,s.r-1,s.c为当前点坐标,d为当前位置方向
case 1: {DFS_LF(s.r,s.c-1,1); break;}
case 2: {DFS_LF(s.r+1,s.c,2); break;}
case 3: {DFS_LF(s.r,s.c+1,3); break;}
}
cout<
#include
#include
int w, h, c, a;
int Sx, Sy, Ex, Ey;
int cnt, flag, tag;
int dir[4][2] = {0, -1, -1, 0, 0, 1, 1, 0};
char maze[50][50];
int vis[50][50];
int que[2500][2];
void DFS(int x, int y, int cnt) {
if (maze[x][y] == 'E') {
printf("%d ", cnt);
tag = 0;
return ;
}
if (tag) for (int i = a+c, j = 0; j < 4; j++, i -= c) {
if (i < 0) i = 3;
if (i > 3) i = 0;
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (tx > h || tx < 1 || ty > w || ty < 1) continue;
if (tag && !vis[tx][ty] && maze[tx][ty] != '#') {
a = i;
DFS(tx, ty, cnt+1);
}
}
}
void BFS() {
int fir = 0, sec = 0;
que[sec][0] = Sx;
que[sec++][1] = Sy;
int step = 1;
while(fir < sec && !vis[Ex][Ey]) {
int tmp = sec;
step++;
while(fir < tmp && !vis[Ex][Ey]) {
int x = que[fir][0];
int y = que[fir++][1];
for(int i = 0; i < 4; i++) {
int tx = x + dir[i][0];
int ty = y + dir[i][1];
if (tx > h || tx < 1 || ty > w || ty < 1) continue;
if(!vis[tx][ty] && maze[tx][ty] != '#') {
que[sec][0] = tx;
que[sec++][1] = ty;
vis[tx][ty] = 1;
}
}
}
}
printf("%d\n", step);
}
int main() {
int t, i, j;
scanf("%d", &t);
while (t--) {
memset(maze, 0, sizeof(maze));
memset(vis, 0, sizeof(vis));
scanf("%d %d", &w, &h);
getchar();
for (i = 1; i <= h; i++) {
for (j = 1; j <= w; j++) {
scanf("%c", &maze[i][j]);
if (maze[i][j] == 'S') Sx = i, Sy = j;
else if (maze[i][j] == 'E') Ex = i, Ey = j;
}
getchar();
}
for (i = 0; i < 4; i++)
if (maze[Sx+dir[i][0]][Sy+dir[i][1]] == '.') break;
vis[Sx][Sy] = 1;
a = i; tag = 1; c = -1;
DFS(Sx, Sy, 1);
a = i; c = 1; tag = 1;
DFS(Sx, Sy, 1);
BFS();
}
return 0;
}
大致题意 要求把一个冰壶从起点“2”用最少的步数移动到终点“3” 其中0为移动区域,1为石头区域,冰壶一旦想着某个方向运动就不会停止,也不会改变方向(想想冰壶在冰上滑动),除非冰壶撞到石头1 或者 到达终点 3 注意的是: 冰壶撞到石头后,冰壶会停在石头前面,此时(静止状态)才允许改变冰壶的运动方向,而该块石头会破裂,石头所在的区域由1变为0. 也就是说,冰壶撞到石头后,并不会取代石头的位置。 终点是一个摩擦力很大的区域,冰壶若到达终点3,就会停止在终点的位置不再移动。 解题思路 其实与其说这是深搜题,我觉得更像模拟题。。。 要先明确: 0为滑动区域 1为石头区域 2为起点,也是可滑动区域 3为终点,不可滑动区域 (1) 起点为“2”,也是一个可滑动的区域,所以标记起点位置之后,可以把起点当做0 (2) 注意区分冰壶是运动的还是静止的,若是静止的话,旁边1格有石头是不能走的。 (3) 输出冰壶从2到3的最短路,如果最短路的步数大于10(不包括10),视作无法走到终点(其实这是用来剪枝的) (4) 滑动过程中冰壶不允许出界 基于上面的原则,不难发现: (1)所谓的“走一步”,就是指冰壶从一个静止状态到下一个静止状态,就是说冰壶在运动时经过的“格数”不视作“步数”,也就是说冰壶每次移动的距离都是不定的。 (2)还有就是由于石头会因为冰壶的碰撞而消失,因此冰壶每“走一步”,场地的环境就会改变一次。 (3)基于(2),可以发现本题虽然是要找 “最短路”,但是BFS几乎不可能,因为每“走一步”,场地的状态就要改变一次;而如果该步不满足要求,又要求把场地的状态还原到前一步,这只有DFS能做到。 (4)基于(3),DFS不是BFS,不能简单地用它来找最短路,必须要把所有可能的路一一找出来,再逐一比较它们的步数才能确定最短。但题目值允许1000MS,此时就面临一个超时的问题。所以题目才同时给出“步数超过10则视为失败”的条件,这是用来剪枝的 有了上面的分析,就能最终确定本题的解法了: 有2种方法: DFS+Vector+剪枝 DFS+回溯+剪枝 但是由于POJ对STL的“兼容性”很差,我用STL就没见过不TLE的题。。。这题也一样,所以不推荐,其实Vector是很好用的,利用它的特性可以在DFS返回上一步时,自动还原场地状态,而回溯法则需要手动还原场地状态(其实也就8行代码) DFS用于寻找路径,回溯(或Vector)用于还原棋盘状态,剪枝用于优化 另外注意的时,如果把冰壶定义为“动静”两种状态,那么搜索时就可以以“格”为单位,+1搜索,详细可以看我的程序,我感觉这样比较简单。我感觉每步都用for找移动的格数会非常烦 下面我会把两种代码都贴出来,供大家参考。 方法一:DFS+回溯+剪枝
/*DFS+回溯+剪枝*/
//Memory Time
//188K 329MS
#include
using namespace std;
const int inf=11;
typedef class
{
public:
int r,c; //冰壶当前位置
bool status; //status冰壶当前状态:运动true ,静止false
}SE;
SE s,e; //记录冰壶起止点
int w,h; //场地size
int MinStep; //最短路
int board[30][30]; //场地
void DFS(int i,int j,bool status,int direction,int step,bool flag)
{ //direction:冰壶当前运动方向 North:0 West:1 South:2 East:3
//flag:是否消除direction方向下一格位置的石头
if(step>10) //剪枝,超过10步的走法就不再考虑了
return;
if(board[i][j]==3) //终点
{
if(MinStep>step)
MinStep=step;
return;
}
if(flag) //消除石头
{
switch(direction)
{
case 0: {board[i-1][j]=0; break;}
case 1: {board[i][j-1]=0; break;}
case 2: {board[i+1][j]=0; break;}
case 3: {board[i][j+1]=0; break;}
}
}
if(!status) //静止
{
if(i-1>=1 && (board[i-1][j]==0 || board[i-1][j]==3)) //North
DFS(i-1,j,true,0,step+1,false);//DFS(i, j, status, direction,step ,flag)
if(j-1>=1 && (board[i][j-1]==0 || board[i][j-1]==3)) //West
DFS(i,j-1,true,1,step+1,false);
if(i+1<=h && (board[i+1][j]==0 || board[i+1][j]==3)) //South
DFS(i+1,j,true,2,step+1,false);
if(j+1<=w && (board[i][j+1]==0 || board[i][j+1]==3)) //East
DFS(i,j-1,true,3,step+1,false);
}
else if(status) //运动
{
switch(direction)
{
case 0:
{
if(i-1<1) //预判下一步是否越界
return;
else
{ //0滑动曲,1石头区,2起点---滑动区,3终点---不可滑动区
if(board[i-1][j]==0) //下一位置为0且不越界,继续运动
DFS(i-1,j,true,0,step,false);
else if(board[i-1][j]==1) //下一位置为1且不越界,停止运动,并消除下一位置的石头
DFS(i,j,false,0,step,true);
else if(board[i-1][j]==3) //下一位置为3且不越界,运动到位置3后停止运动,游戏结束
DFS(i-1,j,false,0,step,false);
}
break;
}
case 1:
{
if(j-1<1) //预判下一步是否越界
return;
else
{
if(board[i][j-1]==0) //下一位置为0且不越界,继续运动
DFS(i,j-1,true,1,step,false);
else if(board[i][j-1]==1) //下一位置为1且不越界,停止运动,并消除下一位置的石头
DFS(i,j,false,1,step,true);
else if(board[i][j-1]==3) //下一位置为3且不越界,运动到位置3后停止运动,游戏结束
DFS(i,j-1,false,1,step,false);
}
break;
}
case 2:
{
if(i+1>h) //预判下一步是否越界
return;
else
{
if(board[i+1][j]==0) //下一位置为0且不越界,继续运动
DFS(i+1,j,true,2,step,false);
else if(board[i+1][j]==1) //下一位置为1且不越界,停止运动,并消除下一位置的石头
DFS(i,j,false,2,step,true);
else if(board[i+1][j]==3) //下一位置为3且不越界,运动到位置3后停止运动,游戏结束
DFS(i+1,j,false,2,step,false);
}
break;
}
case 3:
{
if(j+1>w) //预判下一步是否越界
return;
else
{
if(board[i][j+1]==0) //下一位置为0且不越界,继续运动
DFS(i,j+1,true,3,step,false);
else if(board[i][j+1]==1) //下一位置为1且不越界,停止运动,并消除下一位置的石头
DFS(i,j,false,3,step,true);
else if(board[i][j+1]==3) //下一位置为3且不越界,运动到位置3后停止运动,游戏结束
DFS(i,j+1,false,3,step,false);
}
break;
}
}
}
if(flag) //flag=1表示上一步遇到的是石头,direction表示走该步时的方向。本步表示 回溯前还原石头,即还原上一步的棋盘状态
{
switch(direction)
{
case 0: {board[i-1][j]=1; break;}
case 1: {board[i][j-1]=1; break;}
case 2: {board[i+1][j]=1; break;}
case 3: {board[i][j+1]=1; break;}
}
}
return;
}
int main(void)
{
while(cin>>w>>h)
{
if(!w && !h)
break;
/*Structure the Board*/
MinStep=inf;
for(int i=1;i<=h;i++)
for(int j=1;j<=w;j++)
{
cin>>board[i][j];
if(board[i][j]==2)//起点
{
s.r=i;
s.c=j;
s.status=false;
board[i][j]=0; //记录起点位置后,把它作为0处理
}
if(board[i][j]==3) //终点是特别位置,冰壶经过或到达该格都会停止
{
e.r=i;
e.c=j;
}
}
/*Search the min path*/
DFS(s.r, s.c, s.status, 0, 0, false);
if(MinStep<=10)
cout<
在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别。要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和大小的棋盘,摆放k个棋子的所有可行的摆放方案C。
Input
输入含有多组测试数据。
每组数据的第一行是两个正整数,n k,用一个空格隔开,表示了将在一个n*n的矩阵内描述棋盘,以及摆放棋子的数目。 n <= 8 , k <= n
当为-1 -1时表示输入结束。
随后的n行描述了棋盘的形状:每行有n个字符,其中 # 表示棋盘区域, . 表示空白区域(数据保证不出现多余的空白行或者空白列)。Output
对于每一组数据,给出一行输出,输出摆放的方案数目C (数据保证C<2^31)。
Sample Input
2 1 #. .# 4 4 ...# ..#. .#.. #... -1 -1
Sample Output
2 1
解题思路
DFS,没想法就很难很难,有想法就很容易的题
棋盘规则与否不是难点,无论规则不规则都可以用标记去解决
难点在于 棋盘的行数(列数)n 与 待摆放的棋子总数k 的关系为k<=n
K==n时还是比较好办的
K
//Memory Time
//184K 32MS
#include
using namespace std;
bool chess[9][9];
bool vist_col[9]; //列标记
int status; //状态计数器
int n,k;//棋牌大小,要放棋子个数
void DFS(int row,int num) //逐行搜索,row为当前搜索行,num为已填充的棋子数
{
if(num==k)
{
status++;
return;
}
if(row>n) //配合下面DFS(row+1,num); 语句使用,避免搜索越界
return;
for(int j=1;j<=n;j++)
if(chess[row][j] && !vist_col[j])
{
vist_col[j]=true; //放置棋子的列标记
DFS(row+1,num+1);
vist_col[j]=false; //回溯后,说明摆好棋子的状态已记录,当前的列标记还原
}
DFS(row+1,num); //这里是难点,当k>n>>k)
{
if(n==-1 && k==-1)
break;
/*Initial*/
memset(chess,false,sizeof(chess));
memset(vist_col,false,sizeof(vist_col));
status=0;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
char temp;
cin>>temp;
if(temp=='#')
chess[i][j]=true;
}
DFS(1,0); //注意初始化的值别弄错了 1为当前搜索行,0为已填充的棋子数
cout<
题目大意
一个三维立体空间,有L个平面,每个平面R行、C列(L、R、C都<=30)。,.代表可以走,#代表不能走,S代表开始点,E代表结束点,问从S走到E点,最少可以经过多少分钟,若不能到达,则输出Trapped!
移动方向可以是上,下,左,右,前,后,六个方向
每移动一次就耗费一分钟,要求输出最快的走出时间。
不同L层的地图,相同RC坐标处是连通的Sample Input
3 4 5
S....
.###.
.##..
###.######
#####
##.##
##...#####
#####
#.###
####E1 3 3
S##
#E#
###0 0 0
Sample Output
Escaped in 11 minute(s).
Trapped!
解题思路
我越看这题就越觉得是 XX地下城 = =水题一道,求最短路问题,直接BFS得了
开三维数组,每次搜索方向由二维的4个方向增加到6个,但是方法还是那个方法
没难度
注意如果三维数组恰好开到极限的30*30*30是会RE的,别替人家电脑省空间,想AC就开大点。
值得一提的是。。。这题竟然被那篇经典的 POJ分类 文章归纳到DFS。。。网上发现几个同学还在郁闷地DFS。。。。
这里就提示一下大家,凡是看到求最短路,用DFS一定很难做出来,一定要BFS
//Memory Time
// 784K 32MS
#include
using namespace std;
typedef class
{
public:
int l,r,c;
int depth; //树深(分钟)
}SE;
SE s,e;
bool maze[40][40][40];
int shortminute;
bool BFS(int k,int i,int j)
{
bool vist[40][40][40]={false};
SE queue[30000];
int head,tail;
queue[head=0].l=k;
queue[tail=0].r=i;
queue[0].c=j;
queue[tail++].depth=1;
vist[k][i][j]=true;
while(head>L>>R>>C)
{
if(!L && !R && !C)
break;
/*Initial*/
memset(maze,false,sizeof(maze));
/*Structure the Maze*/
for(k=1;k<=L;k++)
for(i=1;i<=R;i++)
for(j=1;j<=C;j++)
{
char temp;
cin>>temp;
if(temp=='.')
maze[k][i][j]=true;
if(temp=='S')
{
maze[k][i][j]=true;
s.l=k;
s.r=i;
s.c=j;
}
if(temp=='E')
{
maze[k][i][j]=true;
e.l=k;
e.r=i;
e.c=j;
}
}
/*Search the min Minute*/
if(BFS(s.l,s.r,s.c))
cout<<"Escaped in "<
大致题意
给定两个整数n和k通过 n+1或n-1 或n*2 这3种操作,使得n==k
输出最少的操作次数
解题思路
说实话,要不是人家把这题归类到BFS,我怎么也想不到用广搜的= = 自卑ing。。。水题水题,三入口的BFS
注意的地方有二:
1、 由于用于广搜的 队列数组 和 标记数组 相当大,如果定义这两个数组时把它们扔到局部去,编译是可以的,但肯定执行不了,提交就等RE吧= =
大数组必须开为 全局 。。。常识常识。。。
2、 剪枝。直接广搜一样等着RE吧= =
不剪枝的同学试试输入n=0 k=100000。。。。。。铁定RE
怎么剪枝看我程序
//Memory Time
//1292K 0MS
#include
using namespace std;
const int large=200030;
typedef class
{
public:
int x;
int step;
}pos;
int n,k;
bool vist[large]; //数组较大,必须开为全局数组,不然肯定RE
pos queue[large];
void BFS(void)
{
int head,tail;
queue[head=tail=0].x=n;
queue[tail++].step=0;
vist[n]=true;
while(head=0 && !vist[w.x-1]) //w.x-1>=0 是剪枝
{
vist[w.x-1]=true;
queue[tail].x=w.x-1;
queue[tail++].step=w.step+1;
}
if(w.x<=k && !vist[w.x+1]) //w.x<=k 是剪枝
{
vist[w.x+1]=true;
queue[tail].x=w.x+1;
queue[tail++].step=w.step+1;
}
if(w.x<=k && !vist[2*w.x]) //w.x<=k 是剪枝
{
vist[2*w.x]=true;
queue[tail].x=2*w.x;
queue[tail++].step=w.step+1;
}
}
return;
}
int main(void)
{
while(cin>>n>>k)
{
memset(vist,false,sizeof(vist));
BFS();
}
return 0;
}
大致题意
给出一个整数n,(1 <= n <= 200)。求出任意一个它的倍数m,要求m必须只由十进制的’0’或’1’组成。解题思路
首先暴力枚举肯定是不可能的 1000ms 想不超时都难,而且枚举还要解决大数问题。。要不是人家把这题放到搜索,怎么也想不到用BFS。。。
解题方法: BFS+同余模定理
不说废话。
首先说说朴素的不剪枝搜索方法:
我以n=6为例
首先十进制数,开头第一个数字(最高位)一定不能为0,即最高位必为1
设6的 ”01十进制倍数” 为k,那么必有k%6 = 0
现在就是要用BFS求k值
1、先搜索k的最高位,最高位必为1,则此时k=1,但1%6 =1 != 0
因此k=1不是所求,存储余数 1
2、搜索下一位,下一位可能为0,即 k*10+0,此时k=10,那么k%6=4
可能为1,即 k*10+1,此时k=11,那么k%6=5
由于余数均不为0,即k=10与k=11均不是所求
3、继续搜索第三位,此时有四种可能了:
对于k=10,下一位可能为0,即 k*10+0,此时k=100,那么k%6=4
下一位可能为1,即 k*10+1,此时k=101,那么k%6=5
对于k=11,下一位可能为0,即 k*10+0,此时k=110,那么k%6=2
下一位可能为1,即 k*10+1,此时k=111,那么k%6=3
由于余数均不为0,即k=100,k=101,k=110,k=111均不是所求
4、继续搜索第四位,此时有八种可能了:
对于k=100,下一位可能为0,即 k*10+0,此时k=1000,那么k%6=4
下一位可能为1,即 k*10+1,此时k=1001,那么k%6=5
对于k=101,下一位可能为0,即 k*10+0,此时k=1010,那么k%6=2
下一位可能为1,即 k*10+1,此时k=1011,那么k%6=3
对于k=110,下一位可能为0,即 k*10+0,此时k=1100,那么k%6=2
下一位可能为1,即 k*10+1,此时k=1101,那么k%6=3
对于k=111,下一位可能为0,即 k*10+0,此时k=1110,那么k%6=0
下一位可能为1,即 k*10+1,此时k=1111,那么k%6=1
我们发现k=1110时,k%6=0,即1110就是所求的倍数
从上面的演绎不难发现,用BFS是搜索 当前位数字 (除最高位固定为1),因为每一位都只有0或1两种选择,换而言之是一个双入口BFS
本题难点在于搜索之后的处理:对余数的处理,对大数的处理,余数与所求倍数间的关系
#include
#include
#include
using namespace std;
const int N = 1000000;
int flag, mod, cnt, i, j;
int prior[N], prbit[N];
char s[110];
int next[2] = {1, 0};
struct mul { int num, prev; };
struct Queue {
mul q[N*10];
int front, rear;
};
Queue Q;
void BFS() {
int a, b, c, d;
flag = cnt = 1;
mul sta = {1, 1};
Q.front = Q.rear = 0;
Q.q[Q.rear] = sta;
while (flag) {
a = Q.q[Q.front].num;
b = Q.q[Q.front++].prev;
for (j = 0; j < 2 && flag; j++) {
prior[++cnt] = b;
prbit[cnt] = next[j];
c = (a*10 + next[j]) % mod;
if (c == 0) {
i = 0;
d = cnt;
while (d != 1) {
s[i++] = prbit[d]+'0';
d = prior[d];
}
s[i++] = '1';
flag = 0;
break;
}
mul temp = {c, cnt};
Q.q[++Q.rear] = temp;
}
}
}
int main() {
while (scanf("%d", &mod), mod) {
memset(s, 0, sizeof(s));
BFS();
reverse(s, s+i);
puts(s);
}
return 0;
}
n=6
1%6=1 (k=1)
{
(1*10+0)%6=4 (k=10)
{
(10*10+0)%6=4 (k=100)
{
(100*10+0)%6=4 (k=1000)
(100*10+1)%6=5 (k=1001)
}
(10*10+1)%6=5 (k=101)
{
(101*10+0)%6=2 (k=1010)
(101*10+1)%6=3 (k=1011)
}
}
(1*10+1)%6=5 (k=11)
{
(11*10+0)%6=2 (k=110)
{
(110*10+0)%6=2 (k=1100)
(110*10+1)%6=3 (k=1101)
}
(11\*10+1)%6=3 (k=111)
{
(111\*10+0)%6=0 (k=1110) 有解
(111\*10+1)%6=1 (k=1111) 由于前面有解,这个余数不存储
}
}
}
从上面可以看出余数的存数顺序(逐层存储):
用数组mod[]存储余数,其中mod[0]不使用,由mod[1]开始
那么mod中的余数依次为: 1 4 5 4 5 2 3 4 5 2 3 2 3 0 共14个
即说明我们得到 余数0 之前,做了14步*10的操作,那么当n值足够大的时候,是很容易出现k为大数的情况
(事实上我做过统计,200以内的n,有18个n对应的k值为大数
那么我们再用int去存储k就显得不怎么明智了。
为了处理所有情况,我们自然会想到 是不是应该要用int[]去存储k的每一位?
而又由于k是一个01序列,那能不能 把 *10得到k每一位的问题 转化为模2的操作得到k的每一位(0或1) 呢?
答案是可以的
首先我们利用 同余模定理 对得到余数的方式进行一个优化
(a*b)%n = (a%n *b%n)%n
(a+b)%n = (a%n +b%n)%n
随便抽取上面一条式子为例
前一步 (11*10+0)%6=2 即k=110 , k%6=2
当前步 (110*10+0)%6=2
由同余模定理 (110*10+0)%6 = ((110*10)%6+0%6 )%6 = ((110%6 * 10%6)%6 +0 )%6 = (11*10+0)%6 = 2
所以当前步(110*10+0)%6可以转变为 (2*10+0)%6=2
很显然地,这种处理把k=110 等价于 k=2
即用 前一步操作得到的余数 代替 当前步的k值
而n在200的范围内, 余数值不可能超过3位数, 这就解决了 大数的问题
通过这种处理手法,我们只需在BFS时顺手存储一个 余数数组mod[] ,就能通过mod[i-1]得到mod[i] ,直到mod[i]==0 时结束,
大大减少了运算时间
前面已经提到,n=6时,求余操作进行了14次,对应地,BFS时*10的操作也进行了14次。
令i=14,通过观察发现,i%2恰好就是 6 的倍数的最低位数字
i/2 再令 i%2 ,恰好就是 6 的倍数的 次低位数字。。。
循环这个操作,直到i=0,就能得到 6的 01倍数(一个01队列),倒序输出就是所求
这样就完成了 *10操作到 %2操作的过渡
由于n值有限,只是1到200的整数,因此本题也可以用打表做,通过上面的方法得到结果后,就把1~200的倍数打印出来,重新建立一个程序,
直接打表就可以了。
不过打表比上面介绍的方法快不了多少
//Memory Time
//2236K 32MS
#include
using namespace std;
int mod[524286]; //保存每次mod n的余数
//由于198的余数序列是最长的
//经过反复二分验证,436905是能存储198余数序列的最少空间
//但POJ肯定又越界测试了...524286是AC的最低下限,不然铁定RE
int main(int i)
{
int n;
while(cin>>n)
{
if(!n)
break;
mod[1]=1%n; //初始化,n倍数的最高位必是1
for(i=2;mod[i-1]!=0;i++) //利用同余模定理,从前一步的余数mod[i/2]得到下一步的余数mod[i]
mod[i]=(mod[i/2]*10+i%2)%n;
//mod[i/2]*10+i%2模拟了BFS的双入口搜索
//当i为偶数时,+0,即取当前位数字为0 。为奇数时,则+1,即取当前位数字为1
i--;
int pm=0;
while(i)
{
mod[pm++]=i%2; //把*10操作转化为%2操作,逆向求倍数的每一位数字
i/=2;
}
while(pm)
cout<
大致题意 给定两个四位素数a b,要求把a变换到b 变换的过程要保证 每次变换出来的数都是一个 四位素数,而且当前这步的变换所得的素数 与 前一步得到的素数 只能有一个位不同,而且每步得到的素数都不能重复。 求从a到b最少需要的变换次数。无法变换则输出Impossible 解题思路 超级水题,40入口的BFS + 素数判定 不过剪枝之后就没有40入口了,入口数远小于40 无论是判定素数还是搜索素数,首先排除偶数,这样就剪掉一半枝叶了 判断素数用根号法判断, 如果一个数X不能被 [2,√X] 内的所有素数整除,那么它就是素数 可以判断的复杂度降到logn 注意:千位的变换要保证千位不为0 其实素数也是用来辅助搜索剪枝的
//Memory Time
//212K 16MS
#include
using namespace std;
typedef class
{
public:
int prime;
int step;
}number;
bool JudgePrime(int digit)
{
if(digit==2 || digit==3)
return true;
else if(digit<=1 || digit%2==0)
return false;
else if(digit>3)
{
for(int i=3;i*i<=digit;i+=2)
if(digit%i==0)
return false;
return true;
}
}
int a,b;//输入的两个素数
bool vist[15000];
number queue[15000];//记录队列
void BFS(void)
{
int i; //temporary
int head,tail;
queue[head=tail=0].prime=a;//初始素数
queue[tail++].step=0;
vist[a]=true;
while(head>test;
while(test--)
{
cin>>a>>b;
memset(vist,false,sizeof(vist));
BFS();
}
return 0;
}
题目大意: 已知两堆牌s1和s2的初始状态, 其牌数均为c,按给定规则能将他们相互交叉组合成一堆牌s12,再将s12的最底下的c块牌归为s1,最顶的c块牌归为s2,依此循环下去。 现在输入s1和s2的初始状态 以及 预想的最终状态s12 问s1 s2经过多少次洗牌之后,最终能达到状态s12,若永远不可能相同,则输出”-1″。
Sample Input
2 4 AHAH HAHA HHAAAAHH 3 CDE CDE EEDDCC
Sample Output
1 2 2 -1
解题思路: 很浅白的模拟题= = 不懂为什么别人要把它归类到广搜。。。所以我又重新分类了。。。 直接模拟就可以了,关键在于状态记录,然后判重 若s1和s2在洗牌后的状态,是前面洗牌时已经出现过的一个状态,且这个状态不是预想的状态S12,就说明无论怎样再洗牌都不可能达到S12了,因为这个洗牌操作已经陷入了一个“环” 如果状态没有重复过,则一直模拟洗牌,直至s12出现 记录状态可以用map
vist Map的缺省值为0 知道这个就不难了
//Memory Time
//204K 0MS
#include
#include
#include
另一种方法
#include
#include
#include
#include
#include
using namespace std;
const int N = 210;
int len;
char f[N], s[N], last[N];
string res, tt;
set a;
void init() {
a.clear();
res = last; tt = "";
for (int i = 0; i < len; i++) tt += s[i], tt += f[i];
a.insert(tt);
}
int bfs() {
init();
int ans = 1;
while (1) {
if (tt == res) return ans;
string tmp = "";
for (int i = len; i < 2*len; i++) tmp += tt[i], tmp += tt[i-len];
if (a.count(tmp)) return -1;
else tt = tmp, a.insert(tt), ans++;
}
}
int main() {
int t, ca = 0;
scanf("%d", &t);
while (t--) {
scanf("%d", &len);
scanf("%s %s %s", f, s, last);
printf("%d %d\n", ++ca, bfs());
}
return 0;
}
题目大意
题意:倒水游戏,给两个杯子,最初都是空的,有如下操作: 1.将a杯接满 2.将b杯接满 3.将a杯倒掉 4.将b杯倒掉 5.将a杯水倒到b杯,且不能溢出 6.将b杯水倒到a杯,且不能溢出 题目给出3个数,分别表示ab杯子的容量以及期望的结果。问最少需要多少次。如果不能达到,则输出impossible
Sample Input
3 5 4
Sample Output
6 FILL(2) POUR(2,1) DROP(1) POUR(2,1) FILL(2) POUR(2,1)
解题思路
6入口的BFS把两个桶的某个同一时间的状态看做一个整体,视为初态
可对初态进行六种操作,即FILL(1),FILL(2),DROP(1),DROP(2),POUR(1,2),POUR(2,1)
这6种操作在我的程序中分别用一个整数进行记录;
1z0: 清空z瓶子
2z0: 装满z瓶子
3xy: 从x瓶倒向y瓶
(x,y,z∈{1,2})
这样在输出操作时就能根据数字的特点 进行选择性输出 了
这题有两个难点
第一就是要输出得到容量C的过程。
对于BFS的题而言这种要求是非常刁钻的,回溯每一步时很容易就混淆了下标。解决方法:对于每一步的操作,我们都得对它设置一个pos指针,使它指向前一步操作在queue[]队列的下标,即对下标进行回溯,再顺序输出每一步操作
第二就是状态记录。怎样记录两个瓶子在同一时间点的状态,又能使得搜索标记时为O(1)的复杂度?
瓶子某时刻的残余水量就是该瓶子的状态,设残余水量为k1 k2
开始时我想对k1 k2进行各种运算组合,但都无法保证k1 k2的运算组合是独一无二的。
最后我决定用 ostringstream 在k1 k2 两个数字之间加上一个逗号,把他们变为字符串string,再利用map进行标记,相当成功
最后我简单说说各种操作的数学表达式:
设1瓶的容量为v1,残余水量为k1, 2瓶的容量为v2,残余水量为 k2
那么 fill(1) 相当于 k1=v1 fill(2)相当于k2=v2
drop(1)相当于k1=0 drop(2)相当于k2=0对于Pour(1,2),若k1+k2<=v2,则 k2=k1+k2 , k1=0 (有先后顺序)
否则k1=k1+k2-v2 , k2=v2 (有先后顺序)
Pour(2,1)亦同理
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include
#include
#include
#include
#include
using namespace std;
const int N = 110;
bool vis[N][N];
int a, b, c;
struct node {
int a, b, cur, step;//a的容量a,b的容量b,cur判断需要的所有步骤,完成任务需要的步骤
//cur是全局的,一直在加一;step是在弹出来的记录基础是加一的
node(int a = 0, int b = 0, int c = 0, int s = 0) : a(a), b(b), cur(c), step(s) {}
};
queue q;
int prior[N*N], ans[N*N];
//ans[cur]:步骤cur下的操作;prior[cur]:步骤cur的前一个状态
void solve(int cur) {
if (prior[cur] == -1) return;//prior[0]=-1
solve(prior[cur]);
if (ans[cur] == 1) puts("DROP(1)");//清空1,输出“DROP(1)”
else if (ans[cur] == 2) puts("FILL(1)");//装满1
else if (ans[cur] == 3) puts("POUR(1,2)");//1倒向2
else if (ans[cur] == 4) puts("DROP(2)");
else if (ans[cur] == 5) puts("FILL(2)");
else if (ans[cur] == 6) puts("POUR(2,1)");
}
bool bfs() {
int cnt = 0;//第cnt步操作
prior[0] = -1;
q.push(node());
while (!q.empty()) {
node t = q.front();
q.pop();
if (t.a == c || t.b == c) {
printf("%d\n", t.step);
solve(t.cur);
return 1;
}
int sum = t.a + t.b;
if (t.a) {//如果a中有水
if (!vis[0][t.b]) {//a中没有水,b中满水
q.push(node(0, t.b, ++cnt, t.step + 1));//一开始目标cur=c
prior[cnt] = t.cur;
vis[0][t.b] = 1;//a中没有水,b中满水
ans[cnt] = 1;//DROP(1)清空1
}
int db = b - t.b;//b中可以装水
if (t.a >= db && !vis[t.a - db][b]) {//a中的水多于b中可以装的 且 没有过a中水量t.a-db,b中水量为b的状态
q.push(node(t.a - db, b, ++cnt, t.step + 1));
prior[cnt] = t.cur, vis[t.a - db][b] = 1;
ans[cnt] = 3;//POUR(1,2)1倒向2
}
if (t.a < db && !vis[0][sum]) {
q.push(node(0, sum, ++cnt, t.step + 1));
prior[cnt] = t.cur; vis[0][sum] = 1;
ans[cnt] = 3;//POUR(1,2)
}
}
else {//如果a中没有水
if (t.a != a && !vis[a][t.b]) {
q.push(node(a, t.b, ++cnt, t.step + 1));//把a中装满水,b=0,cur=1,step=1
prior[cnt] = t.cur, vis[a][t.b] = 1;
ans[cnt] = 2;//FILL(1)装满1
}
}
if (t.b) {//如果b中有水
if (!vis[t.a][0]) {
q.push(node(t.a, 0, ++cnt, t.step + 1));
prior[cnt] = t.cur, vis[t.a][0] = 1;
ans[cnt] = 4;//DROP(2)
}
int da = a - t.a;
if (t.b >= da && !vis[a][t.b - da]) {
q.push(node(a, t.b - da, ++cnt, t.step + 1));
prior[cnt] = t.cur, vis[a][t.b - da] = 1;
ans[cnt] = 6;//POUR(2,1)
}
if (t.b < da && !vis[sum][0]) {
q.push(node(sum, 0, ++cnt, t.step + 1));
prior[cnt] = t.cur; vis[sum][0] = 1;
ans[cnt] = 6;//POUR(2,1)
}
}
else {
if (b != t.b && !vis[t.a][b]) {
q.push(node(t.a, b, ++cnt, t.step + 1));//b中加满水
prior[cnt] = t.cur, vis[t.a][b] = 1;//cur=2
ans[cnt] = 5;//FILL(2)
}
}
}
return 0;
}
int main() {
cin>>a>>b>>c;
if (!bfs()) puts("impossible");
return 0;
}
//Memory Time
//232K 32MS
#include
#include
#include
#include
另一种方法(不用STL)
#include
#include
#include
#include
using namespace std;
const int N = 110;
bool vis[N][N];
int a, b, c;
struct node {
int a, b, cur, step;
node(int a = 0, int b = 0, int c = 0, int s = 0) : a(a), b(b), cur(c), step(s) {}
};
queue q;
int prior[N*N], ans[N*N];
void solve(int cur) {
if (prior[cur] == -1) return ;
solve(prior[cur]);
if (ans[cur] == 1) puts("DROP(1)");//puts(s) 等效于printf("%s\n",s),前提 :s是C风格字符串,最后以'\0'结尾。
else if (ans[cur] == 2) puts("FILL(1)");
else if (ans[cur] == 3) puts("POUR(1,2)");
else if (ans[cur] == 4) puts("DROP(2)");
else if (ans[cur] == 5) puts("FILL(2)");
else if (ans[cur] == 6) puts("POUR(2,1)");
}
bool bfs() {
int cnt = 0;
prior[0] = -1;
q.push(node());
while (!q.empty()) {
node t = q.front(); q.pop();
if (t.a == c || t.b == c) {
printf("%d\n", t.step);
solve(t.cur);
return 1;
}
int sum = t.a + t.b;
if (t.a) {
if (!vis[0][t.b]) {
q.push(node(0, t.b, ++cnt, t.step+1));
prior[cnt] = t.cur, vis[0][t.b] = 1;
ans[cnt] = 1;
}
int db = b - t.b;
if (t.a >= db && !vis[t.a-db][b]) {
q.push(node(t.a-db, b, ++cnt, t.step+1));
prior[cnt] = t.cur, vis[t.a-db][b] = 1;
ans[cnt] = 3;
}
if (t.a < db && !vis[0][sum]) {
q.push(node(0, sum, ++cnt, t.step+1));
prior[cnt] = t.cur; vis[0][sum] = 1;
ans[cnt] = 3;
}
}
else {
if (t.a != a && !vis[a][t.b]) {
q.push(node(a, t.b, ++cnt, t.step+1));
prior[cnt] = t.cur, vis[a][t.b] = 1;
ans[cnt] = 2;
}
}
if (t.b) {
if (!vis[t.a][0]) {
q.push(node(t.a, 0, ++cnt, t.step+1));
prior[cnt] = t.cur, vis[t.a][0] = 1;
ans[cnt] = 4;
}
int da = a - t.a;
if (t.b >= da && !vis[a][t.b-da]) {
q.push(node(a, t.b-da, ++cnt, t.step+1));
prior[cnt] = t.cur, vis[a][t.b-da] = 1;
ans[cnt] = 6;
}
if (t.b < da && !vis[sum][0]) {
q.push(node(sum, 0, ++cnt, t.step+1));
prior[cnt] = t.cur; vis[sum][0] = 1;
ans[cnt] = 6;
}
}
else {
if (b != t.b && !vis[t.a][b]) {
q.push(node(t.a, b, ++cnt, t.step+1));
prior[cnt] = t.cur, vis[t.a][b] = 1;
ans[cnt] = 5;
}
}
}
return 0;
}
int main() {
scanf("%d %d %d", &a, &b, &c);
if (!bfs()) puts("impossible");
return 0;
}
(3)、简单搜索技巧和剪枝
题目大意
把一个完全图(每个定点都与其他的顶点相关联)分成两部分,使得连接这两部分边的权和最大。即把点分成A、B两组,节点之间的间距已给出,要求解分组的方法,使得∑Cij (i∈A,j∈B)最大。Sample Input
3 0 50 30 50 0 40 30 40 0
Sample Output
90
例(1,2,3),可以分成(1,2)和(3),或者(1,3)和(2),或者(2,3)和(1),然后求每一种可能的值,去最大的那个的值。
第一种可能:1和3之间的值为30,2和3之间的值为40,所以和是70;
第二种可能:1和2之间的值为50,3和2之间的值为40,所以和是90;
第三种可能:2和1之间的值为50,3和1之间的值为30,所以和是80;
即90最大,所以输出90;解题思路1:直接dfs,暴力枚举
思路:1、把这两个集合标记为0和1,先默认所有点都在集合0里。
2、依次枚举每个点id,把每个点都放到集合1里去,这个时候就要调整集合的权值了,原来和id都在集合0里的点,要把权值加上;而在集合1里的点,要把权值减去。
3、权值调整完毕后,和ans比较,如果比ans要大, 调整ans。
4、如果放到集合1中,调整节点后的权值比放在集合0中要大,那么就默认这个点在集合1中,继续枚举下面的点进行DFS。最终是可以把最有状态都枚举出来的。
解题思路2:随机算法图论的无向完全图的最大割问题 (做网络最大流的时候同学们应该看过最小割,所以别问我什么是最大割了。。。不懂的百度去。。。)
可以用 随机化算法 Random Algorithm 去做
解题思路1:直接dfs,暴力枚举
#include
#include
using namespace std;
const int maxn = 30;
int n, ans;
int a[maxn][maxn];
int dep[maxn];
void dfs(int id, int data)
{
dep[id] = 1;//把点id 从集合0放到集合1中去
int tmp = data;
for(int i = 1; i <= n; i ++)
{
if(dep[i] == 0)//在集合0中,求集合1中点i与集合0中点id的和
tmp += a[i][id];
else//都在集合1中,减去该边与id的权值
tmp -= a[i][id];
}
if(ans < tmp)
ans = tmp;
for(int i = id + 1; i <= n; i ++)
{
if(tmp > data)//调整节点后的权值比放在集合0中大,就默认该点在集合1中,继续枚举下面的点DFS
{
dfs(i, tmp);
dep[i] = 0;
}
}
}
int main()
{
while(~scanf("%d", &n))
{
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
scanf("%d", &a[i][j]);
memset(dep, 0, sizeof(dep));
ans = 0;
dfs(1, 0);
printf("%d\n", ans);
}
}
解题思路2:随机算法
//Memory Time
//188K 375MS
#include
using namespace std;
const int TimeLimit=2000; //本题时间限制为2000ms
int main(int i,int j)
{
int n;
while(cin>>n)
{
/*Input*/
int w[30][30]={0};
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
cin>>w[i][j];
w[j][i]=w[i][j]; //双向完全图
}
/*Random Algorithm*/
bool subset[30]={false}; //A集:true B集:false
int time=TimeLimit*100; //使随机次数尽可能大,随机结果尽可能接近最优解
long max_w=0; //最大割的权值之和
long sum=0; //当前边割集权和
while(time--)
{
int x=rand()%n+1; //生成随机数 x,对应于总集合的某个结点x
//注意由于使用的结点序号为1~n,对应了数组下标,下标为0的数组元素没有使用
//那么这里必须+1,因为若rand()=n,那么再对n取模结果就为0
//这时就会导致使用了不存在的 [0]结点,本应使用的 [n]结点就被丢弃了
subset[x]=!subset[x]; //改变x所在的集合位置
for(int i=1;i<=n;i++) //由于是完全图,所以每个顶点i都与x相关联,因此要全部枚举
{
if(subset[i]!=subset[x]) //结点i 和 x分别在两个集合内
sum+=w[i][x]; //就是说因为x所在集合的改变,使得割边的个数增加
//割集的原权值 要加上 当前新加入的割边(i,x)的权值
if(i!=x && subset[i]==subset[x]) //结点i 和 x分别在相同的集合内,但他们不是同一元素
sum-=w[i][x]; //就是说因为x所在集合的改变,使得割边的个数减少
//割集的原权值 要减去 当前失去的割边(i,x)的权值
}
if(max_w < sum)
max_w = sum;
}
cout<
方法二 剪枝
include
#include
#include
#include
#include
using namespace std;
const int N = 25;
int c[N][N], sum[N], tmp[N], val[1<<21], a[N];
int ans = 0, n, up;
void dfs(int i, int* a, int k, int t) {
if (i == n) {
if (val[t] == -1) {
int tot = 0;
for (int i = 0; i < k; i++) {
tot += sum[a[i]];
for (int j = 0; j < k; j++) tot -= c[a[i]][a[j]];
}
val[t] = val[up^t] = tot;
ans = max(ans, tot);
}
return ;
}
dfs(i+1, a, k, t);
a[k++] = i;
dfs(i+1, a, k, t | tmp[i]);
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) tmp[i] = 1 << i;
up = (1 << n) - 1;
memset(val, -1, 4*(up+10));
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) scanf("%d", &c[i][j]);
for (int i = 0; i < n; i++) sum[i] = accumulate(c[i], c[i]+n, 0);
dfs(0, a, 0, 0);
printf("%d\n", ans);
return 0;
}
大致题意
公司现在要发明一种新的碎纸机,要求新的碎纸机能够把纸条上的数字切成最接近而不超过target值。比如,target的值是50,而纸条上的数字是12346,应该把数字切成四部分,分别是1、2、34、6。因为这样所得到的和43 (= 1 + 2 + 34 + 6) 是所有可能中最接近而不超过50的。(比如1, 23, 4, 和6 就不可以,因为它们的和不如43接近50,而12, 34, 6也不可以,因为它们的和超过50了。碎纸还有以下三个要求:1、如果target的值等于纸条上的值,则不能切。
2、如果没有办法把纸条上的数字切成小于target,则输出error。如target是1而纸条上的数字是123,则无论你如何切得到的和都比1大。
3、如果有超过一种以上的切法得到最佳值,则输出rejected。如target为15,纸条上的数字是111,则有以下两种切法11、1或者1、11.
你的任务是编写程序对数字进行划分以达到最佳值。Sample Input
50 12346
376 144139
927438 927438
18 3312
9 3142
25 1299
111 33333
103 862150
6 1104
0 0
Sample Output
43 1 2 34 6
283 144 139
927438 927438
18 3 3 12
error
21 1 2 9 9
rejected
103 86 2 15 0
rejected解题思路
用DFS深搜(1) 比如一个6位数n,切成为6个数的话,这6个数的和如果大于目标数aim则不用再搜索了,因为这肯定是所有划分中和最小的,而最小都比目标数大,自然就没有合要求的答案了.
(2) 如何切分,假如以50 12346 为例。
第一步,先切下一个“1”,然后递归去切“2346”;
第二步,再切下一个“12”,然后递归去切“346”;
第三步,再切下一个“123”,然后递归去切“46”;
第四步,再切下一个“1234” 然后递归去切“6”
第五步,再切下“12346”。(3) 切下来的 前面的数字串部分 则加入到划分的和,剩下的部分继续递归,直到剩下的数字串长度为0。 可以用一个int记录划分方式(int p), 如上例的输入为50 12346时,其结果为43 1 2 34 6,那么p=1121,代表把12346划分为4部分,第一部分为第1位,第二部分为第2位,第三部分为第3、4位,第四部分为第5位
(4) 注意在搜索时,必须把n的 剩余数字部分 转化为字符串再搜索,不然若 剩余的数字开头第一位为 0 时,会导致出错。
(5) 剪枝方法:在搜索时若发现部分和 大于(不能等于)aim时,则可结束搜索。
(6) error的判定要在搜索前进行,rejected(多个最优解)的判定要在搜索后判定。
(7) 关于出现相同最优解的标记,每种划分的sum每出现一次标记+1,要使标记为O(1),只需把vist数组开得足够大。N最多为6位数,因此Maxsum=999999
简单的附上一个关于例50 12346的不完全搜索树
省略号为未列出的结点
//Memory Time
//4160K 157MS
#include
#include
#include
using namespace std;
int getlen(int n) //得到n的位长度
{
if(n<10)
return 1;
else if(n<100)
return 2;
else if(n<1000)
return 3;
else if(n<10000)
return 4;
else if(n<100000)
return 5;
else
return 6;
}
int getvalue(char* s,int i) //得到数字字符串s前i位字符(数字)组成的int值
{
int k=i;
int sum=0;
while(k)
{
k--;
sum+=(s[k]-'0')*(int)pow(10.0,(double)(i-k-1));//pow(x,y):计算x的y次方。x、y及函数值都是double型
}
return sum;
}
int gethead(int n,int i) //得到由n的前i位数字构成的int
{
int len=getlen(n);
if(len<=i)
return n;
return n/(int)pow(10.0,(double)(len-i));
}
int gettail(int n,int i) //得到由n的后i位数字构成的int
{
return n%(int)pow(10.0,(double)i);
}
int aim; //目标数
int result; //最优划分的和
int path; //最优划分的划分方式
int sum; //某种划分的和
int p; //某种划分方式
int vist[1000000]; //记录每个sum出现的次数
//999999是当n=999999时的最大和值
void DFS(char* s,int len)
{
if(len==0)
{
vist[sum]++;//该划分和出现的次数加一
if(sum>result && sum<=aim)//比之前的更接近aim
{
result=sum;
path=p;
}
return;
}
for(int i=1;i<=len;i++)
{
int a=getvalue(s,i); //n的前i位字符转变为数字留下,计入部分和
sum+=a; //部分和
if(sum>aim) //剪枝,部分和已经大于aim,无需继续往下搜索
{
sum-=a;
continue;
}
p=p*10+i; //记录划分方式
char b[7]; //构造n的后i位字符序列,继续递归
int j=0;
for(int k=i;k>aim>>s;
int len=strlen(s);
int n=getvalue(s,len); //构造s的数字序列n
if(!aim && !n)
break;
if(aim==n) //目标值与打印纸上的数字一致,由题意不能切
{
cout<aim) //最小和也大于aim,则所有划分都大于aim,没办法切成aim
{
cout<<"error"<1) //最优解多于一个
cout<<"rejected"<
大致题意
九宫格问题,也有人叫数独问题把一个9行9列的网格,再细分为9个3*3的子网格,要求每行、每列、每个子网格内都只能使用一次1~9中的一个数字,即每行、每列、每个子网格内都不允许出现相同的数字。
0是待填位置,其他均为已填入的数字。
要求填完九宫格并输出(如果有多种结果,则只需输出其中一种)
如果给定的九宫格无法按要求填出来,则输出原来所输入的未填的九宫格
Sample Input
1 103000509 002109400 000704000 300502006 060000050 700803004 000401000 009205800 804000107
Sample Output
143628579 572139468 986754231 391542786 468917352 725863914 237481695 619275843 854396127
解题思路
DFS试探,失败则回溯用三个数组进行标记每行、每列、每个子网格已用的数字,用于剪枝
bool row[10][10]; //row[i][x] 标记在第i行中数字x是否出现了
bool col[10][10]; //col[j][y] 标记在第j列中数字y是否出现了
bool grid[10][10]; //grid[k][x] 标记在第k个3*3子格中数字z是否出现了
row 和 col的标记比较好处理,关键是找出grid子网格的序号与 行i列j的关系
即要知道第i行j列的数字是属于哪个子网格的
首先我们假设子网格的序号如下编排:
由于1<=i、j<=9,我们有:
令a= i/3 , b= j/3 (其中“/”是C++中对整数的除法),根据九宫格的 行列 与 子网格k 的 关系,我们有:
不难发现 3a+b=k
即 3*(i/3)+j/3=k
又我在程序中使用的数组下标为 1~9,grid编号也为1~9
因此上面的关系式可变形为 3*((i-1)/3)+(j-1)/3+1=k
有了这个推导的关系式,问题的处理就变得非常简单了,直接DFS即可
//Memory Time
//184K 422MS
#include
using namespace std;
int map[10][10]; //九宫格
bool row[10][10]; //row[i][x] 标记在第i行中数字x是否出现了
bool col[10][10]; //col[j][y] 标记在第j列中数字y是否出现了
bool grid[10][10]; //grid[k][x] 标记在第k个3*3子格中数字z是否出现了
//(这里说明的字母不代表下面程序中的变量)
bool DFS(int x,int y)//x表示第x行
{
if(x==10)
return true;
bool flag=false;
if(map[x][y])
{
if(y==9)
flag=DFS(x+1,1);
else
flag=DFS(x,y+1);
if(flag) //回溯
return true;
else
return false;
}
else
{
int k=3*((x-1)/3)+(y-1)/3+1;
for(int i=1;i<=9;i++) //枚举数字1~9填空
if(!row[x][i] && !col[y][i] && !grid[k][i])//数字i没有在这一行出现过
{
map[x][y]=i;
row[x][i]=true;
col[y][i]=true;
grid[k][i]=true;
if(y==9)
flag=DFS(x+1,1);//下一行
else
flag=DFS(x,y+1);//该行的下一个格
if(!flag) //回溯,继续枚举
{
map[x][y]=0;
row[x][i]=false;
col[y][i]=false;
grid[k][i]=false;
}
else
return true;
}
}
return false;
}
int main(int i,int j)
{
int test;
cin>>test;
while(test--)
{
/*Initial*/
memset(row,false,sizeof(row));
memset(col,false,sizeof(col));
memset(grid,false,sizeof(grid));
/*Input*/
char MAP[10][10];
for(i=1;i<=9;i++)
for(j=1;j<=9;j++)
{
cin>>MAP[i][j];
map[i][j]=MAP[i][j]-'0';
if(map[i][j])
{
int k=3*((i-1)/3)+(j-1)/3+1;
row[i][ map[i][j] ]=true;
col[j][ map[i][j] ]=true;
grid[k][ map[i][j] ]=true;
}
}
/*Fill the Sudoku*/
DFS(1,1);
for(i=1;i<=9;i++)
{
for(j=1;j<=9;j++)
cout<
大概题意
当一个广播电台在一个非常大的地区,广播站会用中继器来转播信号以使得每一个接收器都能接收到一个强烈的信号。
然而,每个中继器必须慎重选择使用,使相邻的中继器不互相干扰。如果相邻的中继器使用不同的频道,那么就不会相互干扰。由于无线电频道是一有限的,一个给定的网络所需的中继频道数目应减至最低。编写一个程序,读取一个中继网络,
然后求出需要的最低的不同频道数。Sample Input
2 A: B: 4 A:BC B:ACD C:ABD D:BC 4 A:BCD B:ACD C:ABD D:ABC 0
Sample Output
1 channel needed. 3 channels needed. 4 channels needed.
建模
一个有N个节点的无向图,要求对每个节点进行染色,使得相邻两个节点颜色都不同,问最少需要多少种颜色?那么题目就变成了一个经典的图的染色问题。
解题思路
对于这题数据范围很小(节点最多26个),所以使用普通的暴力搜索法即可对点i的染色操作:从最小的颜色开始搜索,当i的直接相邻(直接后继)结点已经染过该种颜色时,搜索下一种颜色。
就是说i的染色,当且仅当i的临近结点都没有染过该种颜色,且该种颜色要尽可能小。
要注意题中的一句话
since the repeaters lie in a plane, the graph formed byconnecting adjacent repeaters does not have any line segments that cross.
大致意思就是 “城市所在的世界是一个平面世界,当把城市看做点,相邻城市用边连接时,这些边不能相交”
PS: 在网上很多同学都说用“四色定理”解决这题, 其实不是,四色定理在本题单纯是用来剪枝的,而且由于结点数较少(只有26),剪枝与否对时间影响不大,普通的爆搜也是0ms AC的,不过有兴趣的同学可以看看“四色定理”。
因为当结点数很多时,四色定理的剪枝优势就会体现出来了
我把 暴力搜索 和 经过四色定理剪枝的搜索 两个程序都都贴出来,大家比较一下就知道四色定理怎么用了。
附:
四色定理的“相邻”是指两块多边形地区“至少一条边重合”才为之相邻“至少一条边重合”同时也隐含了“任意边(线段)不正规相交,如:图1。
再反观本题的模型,本题的相邻是“两点有边连接,但任意两边不交叉(正规相交)”,这种“相邻”其实就是四色定理的“相邻”。我举一个例子就一目了然了:
N=7
A:BCDEFG
B:ACDEFG
C:ABD
D:ABCE
E:ABDF
F:ABEG
G:ABF
画成图就是:图2
PS:由于边不允许相交,这已经是7个点的最大连接数四色定理的原始理论依据
对于一个散点集,若要求尽可能连接任意两个点,但任意一条边边不允许与其他边相交,那么当散点集的元素个数<=4时,连接所得的图必为一个一个 无向完全图(平面图的定理)
当散点集的元素个数>4时,连接所得的图必不是一个完全图
完全图:任意两点均相邻
最后千万要注意输出,当频道数大于1时,channel为复数 channels
方法一: 四色定理剪枝的搜索
Default
图1
图2
方法一暴力搜索
#include
#include
using namespace std;
const int N = 30;
bool g[N][N];
char s[N<<1];
int val[N], n;//val[i]表示第i个点的颜色;n表示节点个数
bool cal(int x, int v) {//判断第x个点能不能染第v种色
for (int i = 0; i < n; i++)
if (g[x][i] && val[i] == v) return 0;//g[x][i]表示冒号后面的点记录;存在连接点g[x][i],且该点染了第V中颜色。则x不能染这个颜色了
return 1;
}
bool dfs(int x, int num) {
if (x == n) return 1;
for (int i = 1; i <= num; i++) {
if (cal(x, i)) {
val[x] = i;
if (dfs(x+1, num)) return 1;
}
}
return 0;
}
int main() {
while (scanf("%d", &n), n) {
for (int i = 0; i < n; i++) {
memset(g[i], 0, 4*n);
val[i] = 0;
scanf("%s", s);
for (int j = 2; s[j]; j++) g[i][s[j]-'A'] = 1;//第i个点所连接的点
}
int ans = 0;
for (int i = 1; !ans; i++)//i种颜色
if (dfs(0, i)) ans = i;
ans == 1 ? puts("1 channel needed.") : printf("%d channels needed.\n", ans);
}
return 0;
}
方法二 四色定理
/*四色定理*/
//Memory Time
//184K 0MS
#include
using namespace std;
typedef class
{
public:
int next[27]; //直接后继
int pn; //next[]指针(后继个数)
}point;
int main(int i,int j,int k)
{
int n;
while(cin>>n)
{
if(!n)
break;
getchar(); //n的换行符
point* node=new point[n+1]; //结点
/*Structure the Map*/
for(i=1;i<=n;i++)
{
getchar(); //结点序号
getchar(); //冒号
if(node[i].pn<0) //初始化指针,表示节点i与几个点相邻
node[i].pn=0;
char ch;
while((ch=getchar())!='\n')
{
j=ch%('A'-1); //把结点字母转换为相应的数字,如A->1 C->3
node[i].next[ ++node[i].pn ]=j;//第i个节点连接的第pn个元素是j
}
}
int color[27]={0}; //color[i]为第i个结点当前染的颜色,0为无色(无染色)
color[1]=1; //结点A初始化染第1种色
int maxcolor=1; //当前已使用不同颜色的种数
for(i=1;i<=n;i++) //枚举每个顶点
{
color[i]=n+1; //先假设结点i染最大的颜色
bool vist[27]={false}; //标记第i种颜色是否在当前结点的相邻结点染过
for(j=1;j<=node[i].pn;j++) //枚举顶点i的所有后继
{
int k=node[i].next[j];
if(color[k]) //顶点i的第j个直接后继已染色
vist[ color[k] ]=true; //标记该种颜色
}
for(j=1;i<=n;j++) //从最小的颜色开始,枚举每种颜色
if(!vist[j] && color[i]>j) //注意染色的过程是一个不断调整的过程,可能会暂时出现大于4的颜色
{ //因此不能单纯枚举4种色,不然会WA
color[i]=j;
break;
}
if(maxcolor