搜索算法

搜索是一种有目的地枚举问题的解空间中部分或全部情况,进而找到解的方法。然后,与枚举策略相比,搜索通常是有目的的查找,发现解空间的某一子集内不存在解时,它便会放弃对该子集的搜索,而不像枚举那般逐个地检查子集内的解是否为问题的解。

1.宽度优先搜索

宽度优先搜索策略从搜索的起点开始,不断地优先访问当前结点的邻居。也就是说,首先访问起点,然后依次访问起点尚未访问的邻居结点,再按照访问起点邻居的先后顺序依次访问它们的邻居,直到找到解或搜遍整个解空间。

POJ Catch That Cow

Description

Farmer John has been informed of the location of a fugitive cow and wants to catch her immediately. He starts at a point N (0 ≤ N ≤ 100,000) on a number line and the cow is at a point K (0 ≤ K ≤ 100,000) on the same number line. Farmer John has two modes of transportation: walking and teleporting.

* Walking: FJ can move from any point X to the points X - 1 or X + 1 in a single minute
* Teleporting: FJ can move from any point X to the point 2 × X in a single minute.

If the cow, unaware of its pursuit, does not move at all, how long does it take for Farmer John to retrieve it?

Input

Line 1: Two space-separated integers: N and K

Output

Line 1: The least amount of time, in minutes, it takes for Farmer John to catch the fugitive cow.

Sample Input

5 17

Sample Output

4

Hint

The fastest way for Farmer John to reach the fugitive cow is to move along the following path: 5-10-9-18-17, which takes 4 minutes.

题目大意

分析

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>

using namespace std;

const int MAXN = 100001;//N和K的范围都是0到100000

struct Status{
    int n,t;//定义当前状态两个参数,位置n,和时间t
    Status(int n,int t):n(n), t(t){}//结构体构造
};

bool visit[MAXN];

int BFS(int n,int k){
    queue<Status> myQueue;
    myQueue.push(Status(n, 0 ));//压入初始状态
    visit[n] = true;//起始点已被访问
    while(!myQueue.empty()){
        Status current = myQueue.front();//取出队列的头作为当前的状态
        myQueue.pop();//出队操作,将队列头删除,即删除当前的状态
        if(current.n == k){
            return current.t;//如果当前状态的n到达了搜索的最终条件,即返回时间t(题目所求的最短时间)
        }
        for (int i = 0; i < 3; ++i) {//题目中涉及到了3种状态转移函数
            Status next(current.n, current.t+1);//初始化下一个状态值(利用结构体构造函数,先初始化为当前的位置,但是时间+1)
            if(i == 0){
                next.n +=1 ;
            }else if(i == 1){
                next.n -= 1;
            }else{
                next.n *=2;
            }
            if(next.n < 0 || next.n >= MAXN || visit[next.n]){
                continue;//判断新状态是否合法以及是否以及被遍历过
            }
            myQueue.push(next);//进队操作,将新状态压入队列中
            visit[next.n] = true;// 将此状态的位置设置为被遍历过
        }
    }

}

int main(){
    int n, k;
    scanf("%d%d",&n,&k);
    memset(visit,false,sizeof(visit));
    printf("%d\n",BFS(n,k));
}

POJ Find The Multiple

Description

Given a positive integer n, write a program to find out a nonzero multiple m of n whose decimal representation contains only the digits 0 and 1. You may assume that n is not greater than 200 and there is a corresponding m containing no more than 100 decimal digits.

Input

The input file may contain multiple test cases. Each line contains a value of n (1 <= n <= 200). A line containing a zero terminates the input.

Output

For each value of n in the input print a line containing the corresponding value of m. The decimal representation of m must not contain more than 100 digits. If there are multiple solutions for a given value of n, any one of them is acceptable.

Sample Input

2
6
19
0

Sample Output

10
100100100100100100
111111111111111111

题目大意

分析

代码

#include<iostream>
#include<cstdio>
#include<queue>

using namespace std;

void BFS(int n){
    queue<long long> myQueue;
    myQueue.push(1);//初始状态为1,压入到队列的头
    while(!myQueue.empty()){
        long long current = myQueue.front();
        myQueue.pop();
        if(current % n == 0){
            printf("%lld\n",current);
            return;
        }
        myQueue.push(current*10);
        myQueue.push(current*10 + 1);
    }
}
int main(){
    int n;
    while(scanf("%d", &n) != EOF){
        if(n == 0){
            break;
        }
        BFS(n);
    }
    return 0;
}

KY12 玛雅人的密码

题目描述

玛雅人有一种密码,如果字符串中出现连续的2012四个数字就能解开密码。给一个长度为N的字符串,(2=<N<=13)该字符串中只含有0,1,2三种数字,问这个字符串要移位几次才能解开密码,每次只能移动相邻的两个数字。例如02120经过一次移位,可以得到20120,01220,02210,02102,其中20120符合要求,因此输出为1.如果无论移位多少次都解不开密码,输出-1。

输入描述:

输入包含多组测试数据,每组测试数据由两行组成。
第一行为一个整数N,代表字符串的长度(2<=N<=13)。
第二行为一个仅由0、1、2组成的,长度为N的字符串。

输出描述:

对于每组测试数据,若可以解出密码,输出最少的移位次数;否则输出-1。

输入

5
02120

输出

1

代码

#include<iostream>
#include<queue>
#include<string>
using namespace std;
struct mitery{
    int index;//移位次数
    string s;//密码字符串
    mitery(int i,string ss):index(i),s(ss){}
};
void BFS(string s){
    queue<mitery> myQueue;
    myQueue.push(mitery(0,s));//将初始状态压入队列头
    while(!myQueue.empty()){
        mitery current=myQueue.front();
        myQueue.pop();
        string currentString=current.s;
        if(currentString.find("2012")!=string::npos){
            cout<<current.index<<endl;//如果当前字符串以及可以找到2012
            return ;
        }
        for(int i=0;i<currentString.size()-1;i++){//状态转移的方式:每次只能移动相邻的两个数字
            swap(currentString[i],currentString[i+1]);
            myQueue.push(mitery(current.index+1,currentString));
            swap(currentString[i],currentString[i+1]);
        }
    }
    cout<<-1<<endl;
}
int main(){
    int n;
    string s;
    while(cin>>n>>s){
        BFS(s);
    }
    return 0;
}

2.深度优先搜索

搜索过程中,通过放弃对某些不可能产生结果的子集的搜索,达到提高效率的目的。这样的技术被称为剪枝。

POJ A Knight's Journey

Description

img

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

代码

#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>

using namespace std;

const int MAXN = 30;//列数和行数均不超过8(即8×8)的棋盘
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];//计算下一步的状态(x,y)——>(nx,ny)
            int ny = y + direction[i][1];
            char col = ny + 'A';
            char row = nx + '1';//用A-Z来表示列,1-99来表示横行
            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);//第一行中有一个正整数n,代表数据有n组。
    int caseNumber = 0;
    while (n--){
        scanf("%d%d",&p,&q);//对于每组数据,都含有两个正整数p和q(1 <= p * q <= 26),代表棋盘有p行q列。
        memset(visit,false,sizeof(visit));
        cout<<"Scenrio #"<<++caseNumber<<":"<<endl;
        visit[0][0]=true;//题目要求在所有可行的路径中输出字母表排序最小的那个解,这个解必定经过A1
        if(!DFS(0,0,1,"A1")){//初始状态的坐标是A1(0,0,1,“A1”)
            cout<<"impossible"<<endl<<endl;
        }

    }
}

POJ Square

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".

Sample Input

3
4 1 1 1 1
5 10 20 30 40 50
8 1 7 2 6 4 4 3 5

Sample Output

yes
no
yes

代码

/**
 * 本题剪枝如下:
 * (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;
}

KY59 神奇的口袋

题目描述

有一个神奇的口袋,总的容积是40,用这个口袋可以变出一些物品,这些物品的总体积必须是40。John现在有n个想要得到的物品,每个物品的体积分别是a1,a2……an。John可以从这些物品中选择一些,如果选出的物体的总体积是40,那么利用这个神奇的口袋,John就可以得到这些物品。现在的问题是,John有多少种不同的选择物品的方式。

输入描述:

输入的第一行是正整数n (1 <= n <= 20),表示不同的物品的数目。接下来的n行,每行有一个1到40之间的正整数,分别给出a1,a2……an的值。

输出描述:

输出不同的选择物品的方式的数目。

输入

3
20
20
20

输出

3

代码

法一:递归

/**
 * 法一 :递归  把物品数目n和物品体积数组a[100]设为全局变量;
 * count(i,sum)表示从数组的第i个数开始往后统计的组合数和为sum的种类数,
 * sum为组合数的和,则:cout(i,sum)=cout(i+1,sum-a[i])+cout(i+1,sum),
 * 其中cout(i+1,sum-a[i])表示包含了a[i],即为从第i+1个数开始往后统计
 * 组合数的和为sum-a[i]的种类数, 而cout(i+1,sum)表示不包含a[i], 即为从第i+1个数开始往后统计组合数的和为sum的种类数 
 */
#include <iostream>
using namespace std;

int a[100];
int n=1;
int count(int i,int sum)
{
    if(sum==0){return 1;} //找到一组和为sum的组合数;
    if(i==n||sum<0) return 0;//i==n说明没有其他的数来组合,sum<0说明组合不出;
    return count(i+1,sum-a[i])+count(i+1,sum);//从数组的第i为开始,包含a[i],和不包含;
}

int main()
{
    while(cin>>n){
        for(int i=0;i<n;i++)
            cin>>a[i];
        cout<<count(0,40)<<endl;
    }
    return 0;
} 

法二:DFS

#include<iostream>
#include<algorithm>
#include<cstring>

using namespace std;
const int MAXN = 25;
const int total = 40; // 总重量
bool visit[MAXN]; // 标记数组
int matter[MAXN]; // 存放物品
int kind = 0; // 记录一共有多少种
int n; // 物品的数量

void DFS(int sum, int position) { // sum为当前已经凑的质量
    if (sum == total) {
        kind++; // 种数增加
        return;
    }
    // 从第一件开始凑数
    for (int i = position; i < n; i++) {
        if (visit[i] || sum + matter[i] > total) {
            continue;
        }
        visit[i] = true;
        DFS(sum + matter[i], i);
        visit[i] = false; // 回溯
    }
}

int main() {
    cin >> n;
    int sum = 0; // 记录所有物品的质量总和
    for (int i = 0; i < n; i++) {
        cin >> matter[i];
        sum += matter[i];
    }
    sort(matter, matter + n);
    // 总和小于40或者最大的已经大于40了
    if (sum < 40 || matter[0] > 40) {
        cout << kind << endl;
        return 0;
    } else {
        memset(visit, false, sizeof(visit));
        DFS(0, 0);
        cout << kind << endl;
    }
    return 0;
}

法三:动态规划

/**
 * 用滚动数组法,每次状态转移仅仅根据前一行数组决定,故选择用一维数组
 * 如果dp[j]已经有了x种方法,而dp[j-volume[i]]也有y种方法
 * 那么由于物体i的存在,对于体积为j的口袋,又能多出dp[j-volume[i]]种方法
 * 例如j=20,volume=10,而有5种方法能装满体积为10的口袋,那么只要有体积为10的物体就一定多出5种装满体积为20的口袋的方法
 * 显而易见,当口袋体积等于该物体体积时,必然有一种方法
 * /而可能不止一个物体有这样的体积,所以每次碰到这个体积的物体,就做一次++运算
 */
#include<cstdio>

int volume[21];//对于所有物品进行遍历,该物体体积为volume[i]
int dp[41];//dp[i]指口袋体积为i时,共有几种方法

int main() {
    int n;
    while (scanf("%d", &n) != EOF) {
        for (int i = 1; i <= n; i++)//输入物品的体积
        {
            scanf("%d", &volume[i]);
        }
        for (int i = 1; i <= n; i++)//对于所有物品进行遍历,该物体体积为volume[i]
        {
            for (int j = 40; j >= volume[i]; j--)//j>=volume[i]因为dp[j]只能从0~40-volume[i]的状态转移而来
            {
                dp[j] += dp[j - volume[i]]; 
            }
            dp[volume[i]]++;
        }             
        printf("%d\n", dp[40]);
    }
    return 0;
}

KY86 八皇后

题目描述

会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将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的皇后串。

输入

2
1
92

输出

15863724
84136275

代码

/**
 * 非常朴素的八皇后问题,问题规模也已经框定好了,
 * 只要把每次得到的列号转化成要比较的十进制数字即可
 */

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>

using namespace std;

vector<int> solut;            //用来放最终的结果
int position[9];            //行号从1开始,其中下标代表行号,其中存放的内容代表列号

void DFS(int row)            //row代表要放入的行号,逐行放入,因为要用的是列号,而且按照习惯都是一列一列计算的
{
    if (row == 9)            //row==9意味着从1~8行全都放入,已完成解
    {
        int temp = 0;
        for (int i = 1; i <= 8; i++) {
            temp += position[i] * pow(10, 8 - i);    //非常朴素的计算方法_(:з」∠)_ 
        }
        solut.push_back(temp);                        //把得到的solution放进vector
    } else {
        for (int i = 1; i <= 8; i++) {
            position[row] = i;        //i在这里代表列号
            bool flag = true;        //用一个标志位来标记,是否冲突
            for (int j = 1; j < row; j++) {
                if (position[row] == position[j] || row - position[row] == j - position[j] ||
                    row + position[row] == j + position[j]) {
                    flag = false;
                    break;
                }        //这里的判断条件j - position[j]会把同一主对角线标记为同一个数字,与row - position[row]同时计算就能判断是否冲突
            }
            if (flag)
                DFS(row + 1);
        }
    }
}

int main() {
    DFS(1);                    //直接从第一行开始放
    sort(solut.begin(), solut.end());    //这里应该不用sort因为得到的solution应该都是从小到大
    int n;
    while (scanf("%d", &n) != EOF) {
        printf("%d\n", solut[n - 1]);    //因为vector是从0开始的    
    }
    return 0;
}
posted @ 2021-03-12 00:52  张吱吱  阅读(363)  评论(0编辑  收藏  举报