计算机考研-机试指南, 第六章:搜索

文章目录

  • 练习题
      • 百鸡问题
      • abc
      • Old Bill
      • 胜利大逃亡
      • 非常可乐
      • 汉诺塔三
      • Prime ring problem.
      • Oil Deposits
      • 全排列
      • Temple of the bone
  • 总结

练习题

百鸡问题

题目链接
题目大意:x+y+z=10, 5x + 3y + 1/3*z = n.求解所有满足的x,y,z.

  • 直接暴力搜索.
  • 为了避免小数,两边同时乘三

代码:

#include
using namespace std;

int main(){
    int n,x,y,z;
    while(cin >> n){
        for(int i=0; i<=100; i++){
            for(int j=0; j<=100; j++){
                if(i * 15 + 9 * j + (100-i-j) <= 3 * n){
                    cout << "x="<

abc

题目链接
a、b、c均是0到9之间的数字,abc、bcc是两个三位数,且有:abc+bcc=532。求满足条件的所有a、b、c的值。

  • 直接爆搜

代码:

#include 
using namespace std;

int main(){
    for(int a=0; a<=9; a++){
        for(int b=0; b<=9;b++){
            for(int c=0;c<=9;c++){
                if(a*100 + b*10+c+b*100+c*10+c==532)
                    cout << a << " " << b << " " << c << endl;
            }
        }
    }
}

Old Bill

题目链接
给一个N, 给出axyzb中的x,y,z,求a,b的值(其中a不为0)使得axyzb%N=0,若有多个解,给出最大的。

  • 直接爆搜

代码:

#include 
using namespace std;

int main(){
    int N,x,y,z,a,b;
    int found = 0;
    while(cin >> N){
        cin >> x >> y >> z;
        found = 0;
        for(a = 9; a>=1; a--){
            for(b=9; b>=1; b--){
                if((a * 10000+x*1000+y*100+z*10+b)%N==0){
                    cout << a << " " << b << " " << (a * 10000+x*1000+y*100+z*10+b)/N << endl;
                    found = 1;
                    break;
                }
            }
        if(found)
            break;
        }
        if(!found)
            cout << 0 << endl;
    }
    return 0;
}

胜利大逃亡

题目链接
题目大意:典型的使用bfs进行迷宫路径搜索问题.

  • 一个grad存地图、一个flag保存是否已经到过该地方、一个队列保存待扩展状态节点。
  • 注意Q.pop()的位置.

代码:

#include
#include
#include
using namespace std;

struct Unit{
    int x,y,z,t;
};
const int max_n = 55;
int grad[max_n][max_n][max_n];
int flag[max_n][max_n][max_n];
queue Q;
void init(){
    memset(flag,0,sizeof(flag));
    while(!Q.empty())
        Q.pop();
}
int m[6][3] = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
int main(){
    int T,A,B,C,t,ans;
    scanf("%d", &T);
    while(T--){
        init();
        scanf("%d%d%d%d", &A, &B, &C, &t);
        for(int i=0; i=0&&nx=0&&ny=0&&nz t)
            printf("-1\n");
        else{
            printf("%d\n", ans);
        }
    }
    return 0;
}

非常可乐

题目链接
题目大意:三个杯子,相互倾倒其中的水,使两个杯子中的水一样多,问是否可行,可行输出最少次数。

  • 抽象为搜索问题。用一个三维数组表示三个杯子中的水
  • 抽象倒的动作为一个函数。
  • 一个状态节点可以扩展6个节点。

代码如下:

#include 
#include 
#include 
using namespace std;

const int max_n = 105;
int flag[max_n][max_n][max_n];
int S,N,M;
struct Unit{
    int s,n,m,t;
};
queue Q;
void init(){
    while(!Q.empty())
        Q.pop();
    memset(flag,0,sizeof(flag));
}

void trans(int &x, int &y, int y_c){
    int left_c = y_c - y;
    if(left_c >= x){
        y = y + x;
        x = 0;
        return;
    }
    y = y_c;
    x = x - left_c;
}
int main(){
    int ans;
    while(cin >> S >> N >> M){
        if(S == 0 && N == 0 && M == 0)
            break;
        init();
        if(S % 2){
            cout << "NO" << endl;
            continue;
        }
        Unit tmp;
        tmp.s = S;
        tmp.n = tmp.m = tmp.t = 0;
        flag[tmp.s][tmp.n][tmp.t] = 1;
        Q.push(tmp);
        while(!Q.empty()){
            Unit top = Q.front();
           // cout << top.s << " " << top.n << " " << top.m << endl;
            if(top.s * 2 == S && (top.m == 0 || top.n == 0) || top.m * 2 == S && (top.s == 0 || top.n == 0) || top.n * 2 == S && (top.s == 0 || top.n == 0)){
                ans = top.t;
                break;
            }
            Q.pop();

            int ns, nn, nm;
            // 从s倒入n
            ns = top.s;
            nn = top.n;
            nm = top.m;
            trans(ns, nn, N);
            if(!flag[ns][nn][nm]){
                Unit tmp;
                tmp.s = ns;
                tmp.n = nn;
                tmp.m = nm;
                tmp.t = top.t + 1;
                flag[ns][nn][nm] = 1;
                Q.push(tmp);
            }

            // s 倒入 m
            ns = top.s;
            nn = top.n;
            nm = top.m;
            trans(ns, nm, M);
            if(!flag[ns][nn][nm]){
                Unit tmp;
                tmp.s = ns;
                tmp.n = nn;
                tmp.m = nm;
                tmp.t = top.t + 1;
                flag[ns][nn][nm] = 1;
                Q.push(tmp);
            }

            // n 倒入 m
            ns = top.s;
            nn = top.n;
            nm = top.m;
            trans(nn, nm, M);
            if(!flag[ns][nn][nm]){
                Unit tmp;
                tmp.s = ns;
                tmp.n = nn;
                tmp.m = nm;
                tmp.t = top.t + 1;
                flag[ns][nn][nm] = 1;
                Q.push(tmp);
            }

            // n 倒入 s
            ns = top.s;
            nn = top.n;
            nm = top.m;
            trans(nn, ns, S);
            if(!flag[ns][nn][nm]){
                Unit tmp;
                tmp.s = ns;
                tmp.n = nn;
                tmp.m = nm;
                tmp.t = top.t + 1;
                flag[ns][nn][nm] = 1;
                Q.push(tmp);
            }

            // m 倒入 n
            ns = top.s;
            nn = top.n;
            nm = top.m;
            trans(nm, nn, N);
            if(!flag[ns][nn][nm]){
                Unit tmp;
                tmp.s = ns;
                tmp.n = nn;
                tmp.m = nm;
                tmp.t = top.t + 1;
                flag[ns][nn][nm] = 1;
                Q.push(tmp);
            }

            // m 倒入 s
            ns = top.s;
            nn = top.n;
            nm = top.m;
            trans(nm, ns, S);
            if(!flag[ns][nn][nm]){
                Unit tmp;
                tmp.s = ns;
                tmp.n = nn;
                tmp.m = nm;
                tmp.t = top.t + 1;
                flag[ns][nn][nm] = 1;
                Q.push(tmp);
            }
        }

        if(Q.empty())
            cout << "NO" << endl;
        else
            cout << ans << endl;
    }
    return 0;
}

汉诺塔三

题目链接
题目大意:汉诺塔问需要移动几次。两个限制:1、大盘不能放小盘下。2、必须往相邻的移动。

  • 个人感觉比较像动态规划。
  • 定义dp[i],i个盘需要次数。目标dp[n]
  • 根具推演,状态转移dp[i] = 3 * dp[i-1] + 2
  • 初态: dp[1] = 2.

代码:

#include 
using namespace std;
const int max_n = 36;
typedef long long LL;

LL dp[max_n];
int main(){

    dp[1] = 2;
    for(int i=2; i> n){
        cout << dp[n] << endl;
    }
    return 0;

}

Prime ring problem.

题目链接
题目大意:有n个数,组成一圈,要求相邻两个数相加为质数。按字典序输出顺时针的所有可能.

  • 使用dfs, 注意使用dfs时脑海中一定要有搜索树的样子。

代码:

#include 
#include 
using namespace std;

const int max_n = 21;
int flag[max_n];
int ans[max_n];
void init(){
    memset(flag, 0, sizeof(flag));
}

int is_prime(int i){
    if(i == 0 || i == 1) return 0;
    for(int j=2; j*j<=i; j++){
        if(i % j == 0)
            return 0;
    }
    return 1;
}
int n;
void dfs(int i){  //寻找第i个数,从0开始
    if(i == n){ // 找完了,但是还需判断和第一个是否为质数
        if(is_prime(ans[n-1] + ans[0])){
            for(int t=0; t> n){
        init();
        ans[0] = 1;
        flag[1] =1;
        cout << "Case " << cnt++ << ":" << endl;
        dfs(1);
        cout << endl;
    }
    return 0;
}

Oil Deposits

题目链接
题目大意: 一个矩阵,两种符号,问其中一种符号的块数。若其和周围八个方向的连在一起为一块。

  • 个人认为这类2维地图类的搜索问题不必抽象搜索树。
  • 一个flag记录是否搜索过该位置,一个grad记录地图。从八个方向扩展节点。具体条件间代码.

代码:

#include
#include
using namespace std;

const int max_n = 105;
int flag[max_n][max_n];
char grad[max_n][max_n];

void init(){
    memset(flag, 0, sizeof(flag));
}
int m, n;
int dirction[8][2] = {{0,1},{1,0},{-1,0},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};
void dfs(int x, int y){
    flag[x][y] = 1;
    for(int i=0; i<8; i++){
        int nx = x + dirction[i][0];
        int ny = y + dirction[i][1];
        if(nx < 0 || nx >= m) continue;
        if(ny < 0 || ny >= n) continue;
        if(grad[nx][ny] == '*') continue;
        if(flag[nx][ny]) continue;
        dfs(nx, ny);
    }
}

int main(){
    while(cin >> m >> n){
        if(m == 0 ) break;
        for(int i=0; i> grad[i][j];
            }
        }

        init();

        int ans = 0;
        for(int i=0; i

全排列

题目链接

题目大意:输出字符串各个字符的全排列,按字典序。

  • 解法一:直接STL。

代码:

#include
#include //映入此库
using namespace std;
int main(){
    string s;
    while(cin>>s){
        sort(s.begin(),s.end());
        do{
            cout<

解法二:手动实现全排列

  • 定义dfs(int i):找寻第i个位置的答案。且字符串从i到最后满足字典序。由于使用了交换策略,因此不需要使用flag标记是否使用过
  • 入口dfs(0), 出口dfs(n)。
  • 出口条件 i==n. 并输出
  • 因为要满足字典序,因此中途不能直接交换.

代码:

#include 
#include 
#include 
#include 
using namespace std;

const int max_n = 7;

char str[max_n];

int len;

void dfs(int i){
    if(i == len)
        printf("%s\n", str);
    char tmp;
    for(int j=i; j=i+1; k--){
            str[k] = str[k-1];
        }
        str[i] = tmp;
        dfs(i+1);
        tmp = str[i];
        for(int k=i+1;k<=j;k++)
            str[k-1] = str[k];
        str[j] = tmp;
    }
}
int main(){
    while(scanf("%s", str)!=EOF){
        len = strlen(str);
        sort(str, str+len);
        dfs(0);
        printf("\n");
    }
    return 0;
}


Temple of the bone

题目链接
题目大意:迷宫逃亡问题,问能否刚好到达, 到达过的地方不能再到达。

  • 注意是刚好到达。因此不能用bfs,而应该采取dfs,搜索每一种路径的存在。
  • 定义dfs(int x, int y, int t)为在时间t是在x,y位置探寻。
  • 出口条件:1: (x,y)是终点但是t不是指定t。 2: t 大于指定t。
  • 注意当找到路径之后,便不要再扩展其他节点了。因此代码中有一句 if(found) return.

代码:

#include 
#include 
using namespace std;

const int max_n = 8;
int flag[max_n][max_n];
char grad[max_n][max_n];

int found;
struct Unit{
    int x,y,t;
};
void init(){
    memset(flag, 0, sizeof(flag));
    found = 0;
}

int dirction[4][2] = {{0,1}, {1,0},{-1,0},{0,-1}};
int M,N,T;
Unit S, D;
void dfs(int x, int y, int t){
    flag[x][y] = 1;
    if(t > T)
        return;
    if(x == D.x && y == D.y){
        if(t == T){
            found = 1;
            return;
        }
        else
            return;
    }
    for(int i=0; i<4; i++){
        int nx = x + dirction[i][0];
        int ny = y + dirction[i][1];
        if(nx < 0 || nx >= M) continue;
        if(ny < 0 || ny >= N) continue;
        if(flag[nx][ny]) continue;
        if(grad[nx][ny] == 'X') continue;
        flag[nx][ny] = 1;
        dfs(nx, ny, t+1);
        if(found) return;
        flag[nx][ny] = 0;

    }


}

int main(){
    while(cin >> M >> N >> T){
        if(M == 0) break;
        init();

        for(int i=0; i> grad[i][j];
                if(grad[i][j] == 'S'){
                    S.x = i;
                    S.y = j;
                    S.t = 0;
                }
                if(grad[i][j] == 'D'){
                    D.x = i;
                    D.y = j;
                }
            }
        }
        dfs(S.x, S.y, S.t);

        if(found)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
}

总结

对本章来说。基本上是三种题型,一种是爆搜,一种是广搜,一种是深搜。主要注意。

  • 暴力搜索

  • 广搜

    • 使用队列存储待扩展状态节点。
    • 注意Q.pop()的位置在break后面.
  • 深搜

    • 主要借助递归实现。
    • 深搜时脑海中一定要有搜索树的图像,其实广搜也要有,但是个人感觉深搜更重要。

其他知识

  • next_permutation()的用法
  • 素数的判断。

你可能感兴趣的:(信息科学)