week4-搜索

1.迷宫

题目描述

给定一个 N* M 方格的迷宫,迷宫里有 T 处障碍,障碍处不可通过。

在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障碍。

给定起点坐标和终点坐标,每个方格最多经过一次,问有多少种从起点坐标到终点坐标的方案。

输入格式

第一行为三个正整数 N,M,T,分别表示迷宫的长宽和障碍总数。

第二行为四个正整数 SX,SY,FX,FY,SX,SY 代表起点坐标,FX ,FY 代表终点坐标。

接下来 T 行,每行两个正整数,表示障碍点的坐标。

输出格式

输出从起点坐标到终点坐标的方案总数。

样例 #1

样例输入 #1

2 2 1
1 1 2 2
1 2

样例输出 #1

1

提示

对于 100% 的数据,1 <= N,M <= 5,1 <= T <= 10,1<= SX,FX <= n,1 <= SY,FY <= m。

显然这是一道搜索题(这不是废话吗),所以我们只需要一个二维数组来存放这个数据,然后用一个数组来标记点位是否被走过,这里需要技巧的是一个用方向数组来模拟前进,(但是当时我竟然没想到,所以写的代码比较冗长).然后判断是否达到终点,如果一条路行不通就要回溯.

注意:这里是要用回溯的dfs来实现这个搜索.

完整代码如下:

#include<bits/stdc++.h>
using namespace std;    
int n,m,num,sx,sy,fx,fy,nx,ny,nowx,nowy,num1=0;    
int book[7][7]={0};  //标记数组
int place[7][7]={0};  //迷宫
void findway(int nowx,int nowy){  //深度优先搜索
    if(nowx==fx&&nowy==fy){  //如果到达目的地,则统计结果,并且回溯
        num1++;
        return;  //一定要回溯
    }
    if(book[nowx+1][nowy]!=1&&nowx+1<=n){  //向下
        book[nowx+1][nowy]=1;
        findway(nowx+1,nowy);
        book[nowx+1][nowy]=0;
    }
    if(book[nowx-1][nowy]!=1&&nowx-1>=1){  //向上
        book[nowx-1][nowy]=1;
        findway(nowx-1,nowy);
        book[nowx-1][nowy]=0;
    }
    if(book[nowx][nowy+1]!=1&&nowy+1<=m){  //向右
        book[nowx][nowy+1]=1;
        findway(nowx,nowy+1);
        book[nowx][nowy+1]=0;
    }
    if(book[nowx][nowy-1]!=1&&nowy-1>=1){  //向左
        book[nowx][nowy-1]=1;
        findway(nowx,nowy-1);
        book[nowx][nowy-1]=0;  //回溯时一定要解除标记
    }
}
int main(){
    cin>>n>>m>>num>>sx>>sy>>fx>>fy;
    for(int i=0;i<num;i++){
        cin>>nx>>ny;
        book[nx][ny]=1;  //输入,并标记障碍物的地方为1
    }
    book[sx][sy]=1;  //起点也要标记为1
    nowx=sx;
    nowy=sy;
    findway(nowx,nowy);  //开始搜索
    cout<<num1;
    system("pause");
    return 0;
}

2.马的遍历

题目描述

有一个 n * m的棋盘,在某个点 (x, y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

输入格式

输入只有一行四个整数,分别为 n, m, x, y。

输出格式

一个 n *m的矩阵,代表马到达某个点最少要走几步(不能到达则输出 -1)。

样例 #1

样例输入 #1

3 3 1 1

样例输出 #1

0    3    2    
3    -1   1    
2    1    4

提示

数据规模与约定

对于全部的测试点,保证 1 <=x<= n <=400,1 <=y <=m<=400。

显然这是一道搜索的题目(我是不是说过这句话),显然这是一道要用到广搜的题目.对于马的移动,我们只需用一个方向数组来实现马的移动,并且最重要的是我们需要一个队列来实现广搜.但是,这还不是最重要的,最重要的是马移动的步数.这里我想了好久才想出来,下面是我的一些见解:

因为广搜只会对队头的元素进行搜索,但不知道这个元素对应的是马的第几步,所以我们就需要自行实现这个功能~~(这好像也是废话)~~,不妨我们画个图看看:

在这里插入图片描述

这是一开始的队列,可以看到一开始的A元素的点只需要0步就可以到达,那么在对第一个元素进行搜索后,应该是下面这样的情况:

在这里插入图片描述

那么假设由A元素可以拓展出4个元素,那么这些元素应该都是只需要1步就可以到达的,那么我们该怎么实现这一功能呢?

我们可以假设在A元素拓展完成后在E元素的地方插一面旗帜,那么直到队头走到E元素的位置,才让num++.然后我们在head走到E元素的位置时再更新旗帜的位置就行了,如下图:

在这里插入图片描述

代码实现如下:

    if(q1.head==flag-1){
        num++;   
        flag2=1;           
    } 
    if(flag2==1){
        flag=q1.tail;
        flag2=0;
    }
    q1.head++;

这里还是没有用到方向数组,所以代码有那么一点点的长,望谅解 qwq.

完整注释代码如下:

#include<iostream>
#include<cmath>
#include<iomanip>
using namespace std;   
struct point{
    int x;  //定义点
    int y;
};
struct q{
    point pointarr[100000]={0};
    int tail;
    int head;  //定义队列
};
q q1;
int n,m,hx,hy,place[405][405]={};
int book[405][405]={0};  //标记数组
void bfs(){   //广搜
    int x1,y1,num=1,flag=q1.head+1,flag2=1;  //为了一开始就让num++
    while(q1.tail>q1.head){  //在队列不为空的情况下执行循环
​    x1=q1.pointarr[q1.head].x+2;
​    y1=q1.pointarr[q1.head].y+1;
​    if(x1<=n&&y1<=m&&book[x1][y1]==0){  //如果没走过就标记它,并且入队
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x+1;
​    y1=q1.pointarr[q1.head].y+2;
​    if(x1<=n&&y1<=m&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x+2;
​    y1=q1.pointarr[q1.head].y-1;
​    if(x1<=n&&y1>=1&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x+1;
​    y1=q1.pointarr[q1.head].y-2;
​    if(x1<=n&&y1>=1&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x-2;
​    y1=q1.pointarr[q1.head].y-1;
​    if(x1>=1&&y1>=1&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x-1;
​    y1=q1.pointarr[q1.head].y-2;
   if(x1>=1&&y1>=1&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x-2;
​    y1=q1.pointarr[q1.head].y+1;
​    if(x1>=1&&y1<=m&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }            
​    x1=q1.pointarr[q1.head].x-1;
​    y1=q1.pointarr[q1.head].y+2;
​    if(x1>=1&&y1<=m&&book[x1][y1]==0){
​        book[x1][y1]=1;
​        q1.pointarr[q1.tail].x=x1;
​        q1.pointarr[q1.tail].y=y1;
​        place[x1][y1]=num;
​        q1.tail++;
​    }        //冗长的代码qwq
​    if(q1.head==flag-1){
​        num++;   
​        flag2=1;           //如果到达旗帜的地方,就让num++,并更行旗帜
​    } 
​    if(flag2==1){
​        flag=q1.tail;  //更行旗帜
​        flag2=0;
​    }
​    q1.head++;        //队头一定要++,要不然不能访问下面的元素
}

}
int main(){
    cin>>n>>m>>hx>>hy;
    book[hx][hy]=1;
    q1.tail=1;
    q1.head=1;
    q1.pointarr[q1.tail].x=hx;
    q1.pointarr[q1.tail].y=hy;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            place[i][j]=-1;
        }
    }
    place[hx][hy]=0;
    q1.tail++;  //初始化
    bfs();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(j==m){
                cout<<place[i][j]<<endl;  //输出
                break;
            }
            cout<<place[i][j]<<"    ";  //输出
        }
    }
    system("pause");
    return 0;
}

终于结束了qwq!

以下为额外的题目:

3.全排列问题

题目描述

按照字典序输出自然数 1 到 n 所有不重复的排列,即 n 的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式

一个整数 n。

输出格式

由 1 ~ n 组成的所有不重复的数字序列,每行一个序列。

每个数字保留 5 个场宽。

样例 #1

样例输入 #1

3

样例输出 #1

1    2    3
1    3    2
2    1    3
2    3    1
3    1    2
3    2    1

提示

1<=n<=9

显然这需要用到dfs(深度优先搜索),我们只需要从1~n开始遍历,看看哪个数据没有被使用,则让它入栈,如果全部数据都已经被使用过了,则输出它们。

完整代码如下:

#include<iostream>
using namespace std;
int book[10]={0};  //标记数组
int arr[10];     //存放数据
int n;
void dfs(int deepth){  //deepth表示搜索的深度
    if(deepth==n){  //如果所有元素都已经使用过了,就输出它们
        for(int i=0;i<n;i++){
            if(i==n-1){      
                cout<<"    "<<arr[i]<<endl;
                break;  
            }
            cout<<"    "<<arr[i];
            return;
        }
    } 
    for(int i=1;i<=n;i++){    
        if(book[i]!=1){  
            book[i]=1;  //如果没有使用过就入栈,然后标记它
            arr[deepth]=i;
            dfs(deepth+1);  //深搜
            book[i]=0;  //回溯时要解除标记
        }
    }
}
int main(){
cin>>n;
dfs(0);   //一开始是深度为0
return 0;
}

4.奇怪的电梯

题目描述

呵呵,有一天我做了一个梦,梦见了一种很奇怪的电梯。大楼的每一层楼都可以停电梯,而且第 i 层楼(1 <=i< =N)上有一个数字 K_i(0 <=K_i <=N)。电梯只有四个按钮:开,关,上,下。上下的层数等于当前楼层上的那个数字。当然,如果不能满足要求,相应的按钮就会失灵。例如: 3, 3, 1, 2, 5 代表了 K_i(K_1=3,K_2=3,……),从 1 楼开始。在 1 楼,按“上”可以到 4 楼,按“下”是不起作用的,因为没有 -2 楼。那么,从 A 楼到 B 楼至少要按几次按钮呢?

输入格式

共二行。

第一行为三个用空格隔开的正整数,表示 N, A, B(1 <=N <= 200,1 <= A, B <= N)。

第二行为 N 个用空格隔开的非负整数,表示 K_i。

输出格式

一行,即最少按键次数,若无法到达,则输出 -1

样例 #1

样例输入 #1

5 1 5
3 3 1 2 5

样例输出 #1

3

提示

对于 100 % 的数据,1 <=N<= 200,1 <= A, B <= N,0 <= K_i <= N。

~~真的会有这么奇怪的电梯吗?~~显然,这道题最核心的思想就是bfs(广度优先搜索),这里我们需要一个数组lift[i]来模拟电梯,lift[i]表示第i层的数字。同时我们还需要一个队列来实现bfs。同时这道题跟“马的遍历”这道题有个相同点,就是我们需要知道现在搜索的层数是由初始层按几次电梯就能到达的。这个点请参考上面的“马的遍历”题解。所以,明白了这么多,我们就能够写出ac代码了。

完整代码如下:

#include<iostream>
using namespace std;
int n,a,b;
int lift[205];  //模拟电梯数组
int book[205]={0};  //标记数组
struct que{
    int head;
    int tail;
    int arr[100000];  //手写队列
};
que q;
int remark=0; 
void bfs(){
    int now=1,num=0,to1,to2,flag1=1,flag2=-1;
    while(q.tail>q.head){  //在队列不为空时执行循环
        now=q.arr[q.head];  //现在所处的楼层
        to1=now+lift[now];  //向上
        to2=now-lift[now];  //向下
        if(now==b){
            cout<<num;
            remark=1;  //如果可以到达目标楼层就标记remark为1表示可以到达
            break;
        }
        if(to1<=n&&book[to1]==0){  
            book[to1]=1;
            q.arr[q.tail]=to1;        
            q.tail++;   //如果向上的楼层在有效范围内且没有走过,就将他们入队
        }
        if(to2>=1&&book[to2]==0){
            book[to2]=1;
            q.arr[q.tail]=to2;
            q.tail++;      //如果向下的楼层在有效范围内且没有走过,就将他们入队
        }        
        if(q.head==flag1){  
            flag2=1;
            num++;  //计数
        }
        if(flag2==1){
            flag1=q.tail-1;  //更新旗帜
            flag2=0;
        }
        q.head++;  //队头一定要加1,否则无法搜索后面的元素
    }
}
int main(){
    cin>>n>>a>>b;
    for(int i=1;i<=n;i++){
        cin>>lift[i];
    }  //输入
    q.head=1;
    q.tail=1;
    q.arr[q.tail]=a;
    q.tail++;  //初始化队列
    bfs();
    if(remark==0){  //如果达到不了就输出-1
        cout<<"-1";
    }
    return 0;
}

5.小埋与扫雷

题目背景

小埋总是在家中打游戏,一天,她突然想玩Windows自带的扫雷,在一旁的哥哥看见了,想起了自己小时候信息课在机房玩扫雷的日子,便兴致勃勃地开始教小埋扫雷。然而,小埋还是不明白 3bv(Bechtel’s Board Benchmark Value,每局将所有非雷的方块点开所需最少左键点击数,参见扫雷网的教程 )怎么算,于是她找到了你。

在这里插入图片描述

题目描述

小埋会告诉你一盘扫雷,用一个n*m 的矩阵表示,1 是雷 ,0 不是雷,请你告诉她这盘扫雷的 3bv。

周围八格没有“雷”且自身不是“雷”的方格称为“空格”,周围八格有“雷”且自身不是“雷”的方格称为“数字”,由“空格”组成的八连通块称为一个“空”。3bv 周围八格没有“空格”的“数字”个数+“空"的个数。

如果看不懂上面的计算方式,可以看题目背景中给出的教程,或者看下面的样例解释。

注:八连通

输入格式

第一行有两个整数 n 和 m,代表这盘扫雷是一个 n * m 的矩阵。

后面的 n 行每行有 m 个整数,表示这个矩阵,每个数字为 0 或 1,1 代表是雷,0 代表不是雷。

输出格式

一个整数,代表这盘扫雷的 3bv。

样例 #1

样例输入 #1

8 8
0 0 0 1 1 0 0 0 
1 0 0 1 0 0 0 1 
1 0 0 1 0 0 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 1 0 0 
0 0 0 0 0 0 0 0 
0 0 0 0 0 0 0 0 
0 1 0 0 0 0 0 0

样例输出 #1

13

提示

1<= n, m<=1000

样例解释

在这里插入图片描述

对于老二次元,这个题目背景好啊(咳咳!)为了这道题目我特地去玩了无数盘扫雷,终于懂了扫雷的玩法。现在我简单的说说扫雷的玩法,因为如果向我一开始不知道扫雷的玩法再去做这道题,我都不知道题目在说些什么东西。

扫雷中的数字格表示以该格为中心的周围八联通块的炸弹的数量,例如数字格4表示以4为中心的八联通块中有4个炸弹(这就很危险了),那些可以一次可以点开的大格子也就是题目中所说的”空“,因为这些格子周围是没有炸弹的(非常的安全),也就是形成这个“空”大格子的这些小格子都是”空格“。所以,根据题目的公式:3bv=周围没有空格的数字格的数量+”空“的数量。

在知道这些信息后,我们就可以有个基本的思路了:

首先,我们要初始化这个雷图,也就是找出数字格和空格,空格也就是周围雷的数量为0的格子,然后我们要知道"空"的数量,这就需要用到dfs来统计,之后将数字格的数量加上“空”的数量就可以得到答案了。

完整代码如下:

#include<iostream>
using namespace std;
int x_d[8]={0,-1,-1,-1,0,1,1,1,},y_d[8]={-1,-1,0,1,1,1,0,-1};   //方向数组(终于不用再先那么冗长的代码了qwq)
bool a[1005][1005];  //标记数组
int zone[1005][1005],n,m,ret,link=0,num=0;  //zone为雷图
int book[1005][1005];  //
void init(){
    int nowx,nowy;
    for(int i=1;i<=n;i++){  
        for(int c=1;c<=m;c++){
            if(zone[i][c]==1){
                zone[i][c]=-1;  //初始化雷图,为防止雷与数字1弄混,把雷标记成-1
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int d=1;d<=m;d++){
            if(zone[i][d]==-1){  
                for(int b=0;b<8;b++){
                    nowx=i+x_d[b];
                    nowy=d+y_d[b];   
                    if(zone[nowx][nowy]!=-1){
                        zone[nowx][nowy]++;   //如果找到雷就将周围8格不是雷的格子自加                      
                    }
                }
            }
        }
    }
}
bool judge(int x,int y){  //判断空格的函数
    int nowx,nowy;
    for(int i=0;i<8;i++){
        nowx=x+x_d[i];
        nowy=y+y_d[i];
        if(nowx>=1&&nowx<=n&&nowy>=1&&nowy<=m&&zone[nowx][nowy]==0){
            return false;  //如果周围有空格就返回假
        }
    }
    return true;  //否则返回真
}
void dfs(int x,int y){  //深搜
    int nowx,nowy;
    for(int i=0;i<8;i++){
        nowx=x+x_d[i];
        nowy=y+y_d[i];
        if(nowx>=1&&nowx<=n&&nowy>=1&&nowy<=m&&zone[nowx][nowy]==0&&!a[nowx][nowy]){  //如果周围8格中有空格就标记它,然后继续搜索
            a[nowx][nowy]=true;
            dfs(nowx,nowy);  //这里不需要回溯
        }
    }
}
int main(){   
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>zone[i][j];  //输入
        }
    }
    init();
    for(int i=1;i<=n;i++){
        for(int c=1;c<=m;c++){
            if(zone[i][c]==0&&!a[i][c]){
                link++;
                a[i][c]=true;
                dfs(i,c);  //如果是空格且没有被标记过就计数然后运用dfs将连在一起的空格都标记
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(zone[i][j]!=-1&&zone[i][j]!=0&&judge(i,j)){  //如果格子是数字格且周围没有空格就计数
                num++;
            }
        }
    }
    ret=num+link;  //计算结果
    cout<<ret;
    return 0;
}

6.01迷宫

题目描述

有一个仅由数字0与1组成的n * n格迷宫。若你位于一格0上,那么你可以移动到相邻4格中的某一格1上,同样若你位于一格1上,那么你可以移动到相邻4格中的某一格0上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式

第1行为两个正整数n,m。

下面n行,每行n个字符,字符只可能是0或者1,字符之间没有空格。

接下来m行,每行2个用空格分隔的正整数i,j,对应了迷宫中第i行第j列的一个格子,询问从这一格开始能移动到多少格。

输出格式

m行,对于每个询问输出相应答案。

样例 #1

样例输入 #1

2 2
01
10
1 1
2 2

样例输出 #1

4
4

提示

所有格子互相可达。

对于20%的数据,n≤10;

对于40%的数据,n≤50;

对于50%的数据,m≤5;

对于60%的数据,n≤100,m≤100;

对于100%的数据,n≤1000,m≤100000。

这道题乍看上去就是用bfs来实现的,一开始我也是这么想的,于是我兴高采烈地写了一个bfs结果完美的超时3个点。

所以,我反思了一下:这里地数据太大了,全部遍历一遍显然要超时,除非有优化的方案或者其他的思路。

所以我就换了个想法,用dfs是否可行?乍看上去显然时不行,但是我又想到了著名地涂色法。于是一个新的思路就诞生了:

因为这道题只求能到达格子的数量,其实在一个连通块地格子所能到达地格子数量是相同的。我们是否可以利用dfs查找联通块,因为查找过的格子就不需要查找了,这就让用时大大降低了。然后我们将联通在一起的格子涂成相同的颜色(这里可以用一个二维数组来模拟涂色),然后再利用一个数组来存放每种颜色所对应的答案,这样在输出答案时,我们只需要查询一下这个格子所对应的颜色,然后输出这个颜色对应的答案就行了。(简直是天才的想法啊)

完整代码如下:

#include<iostream>
using namespace std;
struct point{
    int x;
    int y;
};  //定义点的结构体
int n,m,ans=1,color=1; //color表示要涂的颜色
int paint[1005][1005]; //模拟涂色的二维数组
int colorans[100000];  //储存每种颜色的答案
char maze[1005][1005]; //迷宫的二维数组
int x_d[4]={0,-1,0,1};
int y_d[4]={-1,0,1,0};  //方向数组(简化代码量qwq)
bool book[1005][1005]={false};  //标记数组
void dfs(int x,int y){  //深度优先搜索
    int nowx,nowy,nownum,prenum;
    prenum=maze[x][y]-'0';  //记录之前是什么数字
    for(int i=0;i<4;i++){
        nowx=x+x_d[i];
        nowy=y+y_d[i];
        nownum=maze[nowx][nowy]-'0';
        if(nowx>=1&&nowx<=n&&nowy>=1&&nowy<=n&&book[nowx][nowy]!=1&&nownum!=prenum){
            ans++;
            paint[nowx][nowy]=color;
            book[nowx][nowy]=1;  //如果没有被标记过且数字不相同就对它涂色
            dfs(nowx,nowy);  //继续搜索下一个格子
        }
    }
}
int main(){
    point arr[100005];  
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int a=1;a<=n;a++){
            cin>>maze[i][a];
        }
    }
    for(int i=1;i<=m;i++){
        cin>>arr[i].x;
        cin>>arr[i].y;  //输入
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(!book[i][j]){
                ans=1;  //重置答案
                book[i][j]=1;
                paint[i][j]=color; //如果没有被标记过就标记它,并把它涂色
                dfs(i,j);  //搜索
                colorans[color]=ans;  //记录答案
                color++;  //改变颜色
            }
        }
    }
    for(int i=1;i<=m;i++){
        cout<<colorans[paint[arr[i].x][arr[i].y]]<<endl;  //输出
    }
    return 0;
}

7.八皇后 Checker Challenge

题目描述

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjWI25DJ-1668855312550)(C:\Users\zc\Desktop\补题题解\img\60.png)]

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

行号 1 2 3 4 5 6

列号 2 4 6 1 3 5

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

输入格式

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

输出格式

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

样例 #1

样例输入 #1

6

样例输出 #1

2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4

提示

【数据范围】
对于 100% 的数据,6 <= n <= 13。

题目翻译来自NOCOW。

USACO Training Section 1.5

~~这道题毫无疑问要运用到搜索(我到底说了多少遍这句话啊)~~我们需要一些数组来标记棋子所位于的行,列,两个对角线(以下称为主对角线和副对角线),行和列好说,但是难点就在这两个对角线上。

我该怎么找到两个对角线的特征值呢?

经过苦思冥想,我终于想到了:其实主对角线上元素的行和列的差值是一定的,副对角线上的元素它们行和列的和是一定的。

知道这个后我们就可以很轻易的写出最后的答案了:

完整代码如下:

#include<iostream>
using namespace std;
int n,sum=0;
int remark[15];  //记录列数
int l[15],ding_r[100],ding_l[100];  //l为列,ding_r为主对角线,ding_l为副对角线
int num=0;
void dfs(int deepth){
    if(deepth==n+1){  //如果已经排列完成了就输出并记录结果
        sum++;
        if(num<3){  //只输出三个结果
            for(int i=1;i<deepth;i++){
                if(i==deepth-1){
                    cout<<remark[i]<<endl;
                    break;
                }
                cout<<remark[i]<<" ";
            }            
        }
        num++;
        return ;
    }
    for(int i=1;i<=n;i++){
        if(l[i]!=1&&ding_r[deepth-i+13]!=1&&ding_l[deepth+i]!=1){  //这里用deepth-i+13(行数-列数+13)是为了防止负数
            l[i]=1;  //占领该列
            ding_r[deepth-i+13]=1;  //占领主对角线
            ding_l[deepth+i]=1;  //占领副对角线
            remark[deepth]=i;  //记录列数
            dfs(deepth+1);  //往下搜索
            l[i]=0;  
            ding_r[deepth-i+13]=0;
            ding_l[deepth+i]=0;  //回溯时要解除标记
        }
    }
}
int main(){
    cin>>n;  
    dfs(1);    //从第一行开始搜索
    cout<<sum<<endl;  //输出
    return 0;  //简单的主函数qwq
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值