第一天集训哎ε=(´ο`*))),怎么说感觉自己好菜(╥╯^╰╥),也了解到从搜索拓展出的一些像双向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的题解,好像还可以过八数码难题的说
-
题意:
给定起始状态和结束状态,以及状态转换的规则,求最少的状态转换次数
-
解决:
一般情况下,BFS 第一次遇到末状态时的深度即为“最少次数”,所以使用 BFS 求解
-
优化:
一般的 BFS 通常是从某一状态开始搜索,某节点第一次达到结束状态时停止,属于“单向”搜索(单调向某一方向拓展)
但在本题中给出了固定的起始状态和结束状态,这时可以使用 “ 双向BFS ” 进行优化,顾名思义——即从两个状态开始搜索,这时当两个搜索树第一次出现节点重合就得到了 “ 最少次数 ” 。
-
解释:
① 搜索的停止:从起始状态和结束状态拓展出来的搜索树第一次出现节点重合时,起始点和结束点之间就有一条路径相连接,即求得解。
② 起到优化作用的原因:如果把 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 }
码不动了,┭┮﹏┭┮,今天就先这样吧,还有两道题,之后再慢慢补耶