深度优先算法

A Knight’s Journey

Background
The knight is getting bored of seeing the same black and white squares again and again and has decided to make a journey
around the world. Whenever a knight moves, it is two squares in one direction and one square perpendicular to this. The world of a knight is the chessboard he is living on. Our knight lives on a chessboard that has a smaller area than a regular 8 * 8 board, but it is still rectangular. Can you help this adventurous knight to make travel plans?

Problem
Find a path such that the knight visits every square once. The knight can start and end on any square of the board.
Input

The input begins with a positive integer n in the first line. The following lines contain n test cases. Each test case consists of a single line with two positive integers p and q, such that 1 <= p * q <= 26. This represents a p * q chessboard, where p describes how many different square numbers 1, . . . , p exist, q describes how many different square letters exist. These are the first q letters of the Latin alphabet: A, . . .
Output

The output for every scenario begins with a line containing “Scenario #i:”, where i is the number of the scenario starting at 1. Then print a single line containing the lexicographically first path that visits all squares of the chessboard with knight moves followed by an empty line. The path should be given on a single line by concatenating the names of the visited squares. Each square name consists of a capital letter followed by a number.
If no such path exist, you should output impossible on a single line.
Sample Input

3
1 1
2 3
4 3
Sample Output

Scenario #1:
A1

Scenario #2:
impossible

Scenario #3:
A1B3C1A2B4C2A3B1C3A4B2C4

题目大意: 任选一个起点,按照国际象棋马的跳法,不重复的跳完整个棋盘,如果有多种路线则选择字典序最小的路线(路线是点的横纵坐标的集合,注意棋盘的横坐标的用大写字母,纵坐标是数字)

题目分析:

  1. 应该看到这个题就可以想到用DFS,当首先要明白这个题的意思是能否只走一遍(不回头不重复)将整个地图走完,而普通的深度优先搜索是一直走,走不通之后沿路返回到某处继续深搜。所以这个题要用到的回溯思想,如果不重复走一遍就走完了,做一个标记,算法停止;否则在某种DFS下走到某一步时按马跳的规则无路可走而棋盘还有为走到的点,这样我们就需要撤消这一步,进而尝试其他的路线(当然其他的路线也可能导致撤销),而所谓撤销这一步就是在递归深搜返回时重置该点,以便在当前路线走一遍行不通换另一种路线时,该点的状态是未访问过的,而不是像普通的DFS当作已经访问了。

  2. 如果有多种方式可以不重复走一遍的走完,需要输出按字典序最小的路径,而注意到国际象棋的棋盘是列为字母,行为数字,如果能够不回头走一遍的走完,一定会经过A1点,所以我们应该从A1开始搜索,以确保之后得到的路径字典序是最小的(也就是说如果路径不以A1开始,该路径一定不是字典序最小路径),而且我们应该确保优先选择的方向是字典序最小的方向,这样我们最先得到的路径就是字典序最小的。

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
using namespace std;

const int MAXN=30;
int p,q;//棋盘参数
bool visit[MAXN][MAXN];//标记矩阵

int direction[8][2]={//标记可以走的方向
        {-1,-2},{1,-2},{-2,-1},{2,-1},{-2,1},{2,1},{-1,2},{1,2}
};

bool DFS(int x,int y,int step,string ans){
    if(step==p*q){
        cout<<ans<<endl<<endl;
        return true;//搜索成功
    }
    else{
        for(int i=0;i<8;i++){//遍历邻居结点
            int nx=x+direction[i][0];//扩展状态坐标
            int ny=y+direction[i][1];//扩展状态坐标
            char col=ny+'A';//记录点的编号
            char row=nx+'1';
            if(nx<0||nx>=p||ny<0||ny>=q||visit[nx][ny]){
                continue;
            }
            visit[nx][ny]=true;
            if(DFS(nx,ny,step+1,ans+col+row)){
                return true;//扩展后搜索成功
            }
            visit[nx][ny]=false;//取消标记
        }
    }
    return false;
}

int main(){
    int n;
    scanf("%d",&n);
    int caseNumber=0;
    while(n--){
        scanf("%d%d",&p,&q);
        memset(visit,false,sizeof(visit));
        cout<<"Scenario #"<<++caseNumber<<":"<<endl;
        visit[0][0]=true;//标记A1点
        if(!DFS(0,0,1,"A1")){
            cout<<"imposible"<<endl<<endl;
        }
    }
    return 0;
}

Square

Problem Description
Given a set of sticks of various lengths, is it possible to join them end-to-end to form a square?

Input
The first line of input contains N, the number of test cases. Each test case begins with an integer 4 <= M <= 20, the number of sticks. M integers follow; each gives the length of a stick - an integer between 1 and 10,000.

Output
For each case, output a line containing “yes” if is is possible to form a square; otherwise output “no”.

/**
 * 本题剪枝如下:
 * (1)如果总长不能被4整除,则一定不可以构成正方形
 * (2)如果某根木棍的长度大于边长side,那么必定无法构成正方形
 * (3)如果当前木棍无法构成边,之后可以跳过相同的木棍(需要排序)
 */
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>

using namespace std;

const int MAXN = 25;//棍子的个数是4到20
int side;//边长
int m;//木棍数量
int sticks[MAXN];//木棍长度
bool visit[MAXN];

bool DFS(int sum,int number,int position){//sum是当前拼凑的木棍长度,number是当前已经拼凑成的边长数目,position是当前木棍的编号
    if(number==3){//如果总长能够整除4,且已经拼凑出3条边,那么剩下的木棍一定可以构成最后一条边。
        return true;
    }
    int sample=0;//剪枝(3)
    for (int i = position; i < m; ++i) {
        if (visit[i]||sum+sticks[i]>side||sticks[i]==sample){
            continue;//木棍被标记过、超过了边长、木棍之前被放弃过
        }
        visit[i]=true;//标记该木棍
        if(sum+sticks[i]==side){//恰好形成边
            if(DFS(0,number+1,0)){
                return true;
            }else{
                sample=sticks[i];//记录木棍失败长度
            }
        }else{//没有形成边继续拼凑
            if(DFS(sum+sticks[i],number,i+1)){
                return true;
            }else{
            
                sample=sticks[i];//记录木棍失败长度
            }
        }
        visit[i] = false;
    }
    return false;
}
bool Compare(int x,int y){
    return x>y;
}

int main(){
    int n;
    scanf("%d",&n);//第一行中有一个正整数n,代表数据有n组。
    while(n--){
        int length = 0;//总长
        scanf("%d",&m);//木棍数目
        for (int i = 0; i < m; ++i) {
            scanf("%d",&sticks[i]);//木棍长度
            length += sticks[i];
        }
        memset(visit,false,sizeof(visit));
        if(length%4!=0){//剪枝(1)
            printf("no\n");
            continue;
        }
        side = length / 4;//边长
        sort(sticks,sticks+m,Compare);//从大到小排序
        if(sticks[0]>side){//剪枝(2)
            printf("no\n");
            continue;
        }
        if(DFS(0,0,0)){//初始状态,第一根木棍开始
            printf("yes\n");
        }else{
            printf("no\n");
        }
    }
    return 0;
}

神奇的口袋

描述
有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。
输入描述:
输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。
输出描述:
输出不同的选择物品的方式的数目。

#include<iostream>
#include<cstdio>
using namespace std;

const int maxn=21;
int a[maxn];
int res;//全局变量,代表方案的数目
int n;//代表总的物品数

void dfs(int now,int j){//分别代表当前容量和当前序号
    for(int i=j;i<n;i++){
        int cal=now+a[i];
        if(cal>40) dfs(now,i+1);//当新加入后体积大于40则不要这个产品
        else if(cal<40) dfs(cal,i+1);//当小于40时试着将该产品放进来
        else res++;//当总体积刚好等于40时直接自增次数
    }
}

int main(){
    while(cin>>n){
        res=0;//初始化
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        dfs(0,0);
        printf("%d\n",res);
    }
    return 0;
}

八皇后问题

描述
会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。 对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即a=b1b2…b8,其中bi为相应摆法中第i行皇后所处的列数。已经知道8皇后问题一共有92组解(即92个不同的皇后串)。 给出一个数b,要求输出第b个串。串的比较是这样的:皇后串x置于皇后串y之前,当且仅当将x视为整数时比y小。
输入描述:
每组测试数据占1行,包括一个正整数b(1 <= b <= 92)
输出描述:
输出有n行,每行输出对应一个输入。输出应是一个正整数,是对应于b的皇后串。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值