假期集训1--搜索专题(八皇后、八数码、骑士问题、生日蛋糕、虫食算)

第一天集训哎ε=(´ο`*))),怎么说感觉自己好菜(╥╯^╰╥),也了解到从搜索拓展出的一些像双向bfs,iddfs......这样的算法啦,反正就是,加油奥利给!

1、八皇后问题

一个如下的  6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i个数字表示在第 i 行的相应位置有一个棋子,如下:

行号 1 2 3 4 5 6

列号 2 4 6 1 3 5

这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 个解。最后一行是解的总个数。

输入格式

一行一个正整数 n,表示棋盘是 n×n大小的。

输出格式

前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数

拿到这道题的第一个想法就是dfs。因为我们要保证每个皇后不在同一个对角线,不在一行,不在一列。所以我们每次把第k个皇后放在第k行,即保证每个皇后都不在同一行。接下来我们要判断每个皇后是否在一列或者对角线即可。我们设一个queen数组表示每个皇后所放位置所在列prey == ny || prey-prex == ny-nx || prey + prex == ny + nx。如果以上条件都不成立,那么皇后k的放置就是合理的。把当前合理的位置记录下来,以便下次遍历。

代码如下

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 int Queen[20],n;
 5 int num=0;
 6 int dfs(int k)
 7 {
 8     if (k>n)
 9     {
10         num++;
11     if (num<=3)
12     {
13     for (int i=1;i<=n;i++)
14     cout<<Queen[i]<<" ";
15     cout <<endl;
16 }
17     return 1;
18 }
19     int ans = 0;
20     for (int i = 1 ;i<= n ;i++)
21     {
22     int nx = k, ny = i;
23     bool isOk = true;
24     for (int j = 1; j< k && isOk ;j++)
25     {
26     int prex = j, prey = Queen[j];
27     if (prey == ny || prey-prex == ny-nx || prey + prex == ny + nx)//在同一列、一个对角线 不合法 
28     isOk = false;
29         }
30          if (isOk)
31         {
32          Queen[k] = i;
33          ans += dfs(k+1); 
34         }
35     }
36     return ans;
37 }
38 int main()
39  {
40     scanf ("%d",&n);
41         printf ("%d\n",dfs(1));
42     return 0;
43 }

但是呢,有一个严峻的问题,这道题的最后一个点会华丽丽的t掉!!!扎心的87分,所以我们要做一些优化。

这样我们其实也可以开一个二维数组queen[4][]分别表示行,列和两个方向的对角线,而且因为只需要三组,所以我们只需要搜到三组就可以停止。

这样一个小小的优化我们就可以ac啦

优化后的代码:

 1 #include<iostream>
 2 #include<cstdio>
 3 using namespace std;
 4 int num=0;
 5 int n, ans[50], sum;
 6 bool queen[4][50];
 7 void dfs(int i)
 8 {
 9     int j;
10     if(i>n)
11     {
12         ++sum;
13         if(sum>3) return ;
14         for(int i=1;i<=n;++i)    printf("%d ",ans[i]);
15         printf("\n");
16         return ;
17     }
18 
19     for(j=1;j<=n;++j)
20 
21         if( !queen[1][j] && (!queen[2][i+j]) && (!queen[3][i-j+n]) )
22     {
23         ans[i] = j;
24         queen[1][j] = 1;
25         queen[2][i+j] = 1;
26         queen[3][i-j+n] = 1;
27         dfs(i+1);
28         queen[1][j] = 0;
29         queen[2][i+j] = 0;
30         queen[3][i-j+n] = 0;
31     }
32 }
33 int main()
34 {
35     scanf("%d",&n);
36     dfs(1);
37     printf("%d",sum);
38     return 0;
39 }

2.

题意翻译

输入8*8的国际象棋棋盘上的2个格子(列:a~h,行:1~8),求马至少多少步从起点(键盘输入的第一个位置)跳到终点(键盘输入的第二个位置)。

一个双向bfs的题解,好像还可以过八数码难题的说

  1. 题意:

    给定起始状态和结束状态,以及状态转换的规则,求最少的状态转换次数

  2. 解决:

    一般情况下,BFS 第一次遇到末状态时的深度即为“最少次数”,所以使用 BFS 求解

  3. 优化:

    一般的 BFS 通常是从某一状态开始搜索,某节点第一次达到结束状态时停止,属于“单向”搜索(单调向某一方向拓展)

    但在本题中给出了固定的起始状态和结束状态,这时可以使用 “ 双向BFS ” 进行优化,顾名思义——即从两个状态开始搜索,这时当两个搜索树第一次出现节点重合就得到了 “ 最少次数 ” 。

  4. 解释:

    ① 搜索的停止:从起始状态和结束状态拓展出来的搜索树第一次出现节点重合时,起始点和结束点之间就有一条路径相连接,即求得解。

    ② 起到优化作用的原因:如果把 BFS 搜索树都看成一棵二叉树,那么高度为h的二叉树至多有2^h-1个节点

代码实现:

通常使用两个队列分别储存从两个状态开始的搜索状态,用vis数组判断是否重合,用dis数组储存每个节点的深度,遇到解的时候直接调用dis数组以输出。

而搜索方式一般是:每次都选择节点个数少的那个队列拓展

 1 #include<queue>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 const int N=10;
 6 char a[N],b[N];
 7 int dx[N]={-1,-2,-2,-1,1,2,2,1};
 8 int dy[N]={-2,-1,1,2,2,1,-1,-2};
 9 struct node{
10     int x,y,t;
11 }st,ed,tmp;
12 int dis[N][N],vis[N][N];
13 int bfs()
14 {
15     if(st.x==ed.x&&st.y==ed.y) return 0;
16     queue<node> q1;
17     queue<node> q2;
18     vis[st.x][st.y]=1;
19     vis[ed.x][ed.y]=2;
20     q1.push(st);
21     q2.push(ed);
22     int fx,fy,xx,yy;
23     while(true){
24         if(q1.size()<q2.size()){
25             fx=q1.front().x;
26             fy=q1.front().y;
27             for(int i=0;i<8;i++){
28                 xx=fx+dx[i];yy=fy+dy[i];
29                 if(xx<1||xx>8||yy<1||yy>8) continue;
30                 if(vis[xx][yy]==0){
31                     tmp.t=q1.front().t+1;
32                     tmp.x=xx;tmp.y=yy;
33                     q1.push(tmp);
34                     vis[xx][yy]=1;
35                     dis[xx][yy]=tmp.t;
36                 }
37                 else if(vis[xx][yy]==2)
38                 return dis[xx][yy]+q1.front().t+1;
39             }q1.pop();
40         }
41         else{
42             fx=q2.front().x;
43             fy=q2.front().y;
44             for(int i=0;i<8;i++){
45                 xx=fx+dx[i];yy=fy+dy[i];
46                 if(xx<1||xx>8||yy<1||yy>8) continue;
47                 if(vis[xx][yy]==0){
48                     tmp.t=q2.front().t+1;
49                     tmp.x=xx;tmp.y=yy;
50                     q2.push(tmp);
51                     vis[xx][yy]=2;
52                     dis[xx][yy]=tmp.t;
53                 }
54                 else if(vis[xx][yy]==1)
55                 return dis[xx][yy]+q2.front().t+1;
56             }q2.pop();
57         }
58     }
59 }
60 int main(void)
61 {
62     while(scanf("%s%s",a,b)!=EOF){
63         st.x=a[0]-'a'+1;st.y=a[1]-'0';
64         ed.x=b[0]-'a'+1;ed.y=b[1]-'0';
65         st.t=ed.t=0;
66         memset(vis,0,sizeof(vis));
67         memset(dis,0,sizeof(dis));
68         printf("To get from %c%c to %c%c takes %d knight moves.\n",a[0],a[1],b[0],b[1],bfs());
69     }
70     return 0;
71 }

并不是很懂啊,海,争取结合八数码和这道题理解一下双向bfs吧。

3.八数码难题

题目描述

在3×3的棋盘上,摆有八个棋子,每个棋子上标有1至8的某一数字。棋盘中留有一个空格,空格用0来表示。空格周围的棋子可以移到空格中。要求解的问题是:给出一种初始布局(初始状态)和目标布局(为了使题目简单,设目标状态为123804765),找到一种最少步骤的移动方法,实现从初始布局到目标布局的转变。

输入格式

输入初始状态,一行九个数字,空格用0表示

输出格式

只有一行,该行只有一个数字,表示从初始状态到目标状态需要的最少移动次数(测试数据中无特殊无法到达目标状态数据)

 

首先我们可以将这个3x3的矩阵转化为一个九位数字,存入队列时也会方便很多,按方向尝试0周围的数字

双向BFS的使用要求之一就是知道终止状态, 这里可以将判重数组的值设为0,1,2,分别代表未访问过,顺序访问过,逆序访问过,当某个状态被顺序逆序都访问过时,那么这就是连接答案的那个状态。

 1 #include<bits/stdc++.h>
 2 #define ll long long int
 3 using namespace std;
 4 int n,g=123804765;
 5 short a[4][4],fx,fy,nx,ny;
 6 int dx[4]={1,-1,0,0};
 7 int dy[4]={0,0,1,-1}; 
 8 queue<int> q;
 9 map<int,int> v;
10 map<int,int> ans;
11 void solve()
12 {
13     if(n==g)        
14     {
15         printf("0");
16         exit(0);
17     }               
18     q.push(n);      
19     q.push(g);
20     ans[n]=0;
21     ans[g]=1;       
22     v[g]=2;   
23     v[n]=1;
24     while(!q.empty())
25     {
26         ll now,cur=q.front();
27         q.pop();
28         now=cur;
29         for(int i=3;i>=1;i--)
30             for(int j=3;j>=1;j--)
31             {
32                 a[i][j]=now%10,now/=10;
33                 if(a[i][j]==0) fx=i,fy=j;
34             }
35         for(int i=0;i<4;i++)
36         {
37             nx=fx+dx[i];
38             ny=fy+dy[i];
39             if(nx<1 || nx>3 || ny<1 || ny>3) continue;
40             swap(a[fx][fy],a[nx][ny]);
41             now=0;
42             for(int p=1;p<=3;p++)
43                 for(int j=1;j<=3;j++)
44                     now=now*10+a[p][j]; 
45             if(v[now]==v[cur]) 
46             {
47                 swap(a[fx][fy],a[nx][ny]); 
48                 continue;
49             }
50             if(v[now]+v[cur]==3)      
51             {
52                 printf("%d",ans[cur]+ans[now]);
53                 exit(0);
54             }
55             ans[now]=ans[cur]+1;
56             v[now]=v[cur];             
57             q.push(now);
58             swap(a[fx][fy],a[nx][ny]); 
59         }   
60     }
61 }
62 int main()
63 {
64     scanf("%d",&n);
65     solve();
66 }

码不动了,┭┮﹏┭┮,今天就先这样吧,还有两道题,之后再慢慢补耶

猜你喜欢

转载自www.cnblogs.com/very-beginning/p/12198731.html