搜索

一、暴力求解

特别注意时间复杂度(枚举情况个数)

#define _CRT_SECURE_NO_DEPRECATE
#include<stdio.h>
int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        for (int i = 0; i <= 100; i++)
        {
            for (int j = 0; j <= 100; j++)
            {
                int z = 100 - i - j;
                //避免除法带来的精度损失
                if (15 * i + 9 * j + z <= 3*n)
                {
                    printf("x=%d,y=%d,z=%d\n", i, j, z);
                }

            }
        }
    }
    return 0;
}

为避免除法带来的精度损是,这里采用对不等式两端的所有数字都乘以3,这也是避免除法的 常用技巧

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

#include <stdio.h>

int main()
{
    int N;
    bool flag;
    while (scanf("%d", &N) != EOF)
    {
        flag = false;
        int X, Y, Z;
        scanf("%d%d%d", &X, &Y, &Z);
        int tmp = X * 1000 + Y * 100 + Z * 10;
        for (int i = 9; i > 0; i--)
        {
            for (int j = 9; j >= 0; j--)
            {
                int num = i * 10000 + tmp + j;
                if (num%N == 0)
                {
                    flag = true;
                    printf("%d %d %d\n", i,j,num / N);
                    break;
                }

            }
            if (flag)
               break;  //跳出全部循环
        }
        if (!flag)
            printf("0\n");
    }
    return 0;
}

这里的两个点: 有了最佳答案后,要跳出两层循环,每个输入案例的标志量要初始化
二、广度优先搜索
为了能够考虑搜索的问题,我们常在搜索问题中指明某种状态,从而使搜索问题变为对状态的搜索 采取相应措施来制约状态的无限扩展

第一次查找到包含结点(x,y,z)的状态,其后查找到的任意包含该坐标的状态都不必被扩展,因为其所耗时肯定大于先被查找的状态

为了防止对无效状态的搜索,用一个标记数组,当下次再由某状态扩展出包含该坐标的状态时,则直接丢弃

#include <stdio.h>
#include<queue>
using namespace std;

bool mark[50][50][50]; //标记数组
int maze[50][50][50]; //保存立方体信息
struct N {
    int x, y, z;
    int t;
};
queue<N> Q; //队列中的元素为状态
int go[][3] = {  //坐标变换数组
    1,0,0,
    -1,0,0,
    0,1,0,
    0,-1,0,
    0,0,1,
    0,0,-1
};
int BFS(int a, int b, int c) //返回其最小耗时
{
    while (!Q.empty())
    {
        N now = Q.front();
        Q.pop();
        for (int i = 0; i < 6; i++) //一次扩展其相邻六个方向
        {
            int nx = now.x + go[i][0];
            int ny = now.y + go[i][1];
            int nz = now.z + go[i][2];
            //若新坐标在立体方外,丢弃
            if (nx < 0 || nx >= a || ny < 0 || ny >= b || nz < 0 || nz >= c)
                continue;
            if (maze[nx][ny][nz] == 1)
                continue; //该位置是墙
            if (mark[nx][ny][nz] == true)
                continue; //包含该坐标的状态已经得到过了

            N tmp;
            tmp.x = nx;
            tmp.y = ny;
            tmp.z = nz;
            tmp.t = now.t + 1; //新状态
            Q.push(tmp);
            mark[nx][ny][nz] = true;
            if (nx == a - 1 && ny == b - 1 && nz == c - 1)
                return tmp.t;
        }
    }
    return -1; //所有状态都被查找,仍旧得不到
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int a, b, c, t;
        scanf("%d%d%d%d", &a, &b, &c, &t);
        for (int i = 0; i < a; i++)
        {
            for (int j = 0; j < b; j++)
            {
                for (int k = 0; k < c; k++)
                {
                    scanf("%d", &maze[i][j][k]);
                    mark[i][j][k] = false; //初始化标记数组
                }
            }
        }
        while (!Q.empty())
            Q.pop(); //清空队列
        mark[0][0][0] = true; //标记起点
        N tmp;
        tmp.t = tmp.x = tmp.y = tmp.z = 0; //初始状态
        Q.push(tmp);
        int res = BFS(a, b, c);
        if (res <= t)
            printf("%d\n", res);
        else
            printf("-1\n");
    }
    return 0;
}

平分可乐

思路:
使用四元组(x,y,z,t)来表示一个状态,其中x,y,z分别表示三个瓶子中的可乐体积,t表示从初始状态到该状态所需的杯子间互相倾倒的次数:某一四元组经过瓶子间的相互倾倒而得到若干组新的四元组的过程

while (!Q.empty())
    {
        N now = Q.front();
        Q.pop();
        int a, b, c;
        a = now.a;
        b = now.b;
        c = now.c;
        AtoB(a, s, b, n);
        if (mark[a][b][c] = false)
        {
            mark[a][b][c] = true;
            N tmp;
            tmp.a = a;
            tmp.b = b;
            tmp.c = c;
            tmp.t = now.t + 1;
            if (a == s / 2 && b == s / 2)
                return tmp.t;
            if (c == s / 2 && b == s / 2)
                return tmp.t;
            if (a == s / 2 && c == s / 2)
                return tmp.t;
            //否则放入队列
            Q.push(tmp);
        }
        //重置为a,b,c没有倾倒的体积
        a = now.a;
        b = now.b;
        c = now.c;
        AtoB(b, n, a, s); //b倒向a
        if (mark[a][b][c] = false)
        {
            mark[a][b][c] = true;
            N tmp;
            tmp.a = a;
            tmp.b = b;
            tmp.c = c;
            tmp.t = now.t + 1;
            if (a == s / 2 && b == s / 2)
                return tmp.t;
            if (c == s / 2 && b == s / 2)
                return tmp.t;
            if (a == s / 2 && c == s / 2)
                return tmp.t;
            //否则放入队列
            Q.push(tmp);
        }
             ………………………………………………………………………………
             重复(a倒向c,c倒向a,b倒向c,c倒向a)
}
//初始化
        N tmp;
        tmp.a = s;
        tmp.b = 0;
        tmp.c = 0;
        tmp.t = 0;
        while (!Q.empty())
            Q.pop();
        Q.push(tmp);
        mark[s][0][0] = true;
        int res = BFS(s, n, m);

广度优先搜索的几个关键

  1. 状态:确定求解问题中的几个状态
  2. 状态扩展方式
  3. 有效状态:对有些状态我们并不对其进行再一次的扩展
  4. 队列:将得到的状态依次放入队尾
  5. 标记:判断哪些状态是
  6. 有效状态数:与算法时间复杂度同等数量级
  7. 最优:广度优先搜索常常被用来求解最优值问题

二、递归
递归的两个重要因素: 递归方式和递归出口

int F(int x)
{
   if(x==0) return 1; //递归出口
   else return x*F(x-1); //递归方式
}

汉诺塔变形
每次移动一定是移到中间或从中间移出

F(k) = 3*F(k-1)+2
#include<stdio.h>
long long F(int num)
{
    if (num == 1)
        return 2;
    else
        return 3 * F(num - 1) + 2;
}

int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        printf("%lld\n", F(n));
    }
    return 0;
}

递归的应用:
①回溯法枚举(DFS)

for(int i=2;i<=n;i++)
{
 if(hash[i]==false)
 {
   hash[i] = true;
   ans[num+1] = i;
   DFS(num+1);
   hash[i] = false;
 }
}

最后hash[i] = false; 是因为当DFS调用返回后,意味着当前num+1个数字确定为ans[1]到ans[num+1]中的值,进行新答案的搜索。所以此时ans[num+1]的值不再是已经使用过的x,而是一个新的不同于x,又未在之前使用过。对于后序数字来说,x是未被使用过的,是可以放到后序任意一个位置的
②图的遍历

三、深度优先搜索

在使用深度优先搜索时,我们更多的是求解有或者没有的问题
深度优先搜索对状态的查找采用了立即扩展得到新状态的方法,我们常使用递归函数来完成这一功能

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值