(算法提高课)搜索-DFS之剪枝与优化

165. 小猫爬山
166. 数独 难度偏高

165. 小猫爬山

翰翰和达达饲养了 N只小猫,这天,小猫们要去爬山。
经历了千辛万苦,小猫们终于爬上了山顶,但是疲倦的它们再也不想徒步走下山了。
翰翰和达达只好花钱让它们坐索道下山。
索道上的缆车最大承重量为 W,而 N 只小猫的重量分别是 C 1 、 C 2 … … C N C_1、C_2……C_N C1C2……CN
当然,每辆缆车上的小猫的重量之和不能超过 W。
每租用一辆缆车,翰翰和达达就要付 1美元,所以他们想知道,最少需要付多少美元才能把这 N 只小猫都运送下山?
输入格式
第 1行:包含两个用空格隔开的整数,N和 W。
第 2…N+1行:每行一个整数,其中第 i+1行的整数表示第 i只小猫的重量 C i C_i Ci
输出格式
输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。
数据范围
1≤N≤18
1≤Ci≤W≤108
输入样例:
5 1996
1
2
1994
12
29
输出样例:
2
难度:简单
时/空限制:1s / 64MB
总通过数:23287
总尝试数:49543
来源:《算法竞赛进阶指南》

之前的DFS都是要遍历所有结果,求所有可能的结果;
而在本题中只是要求一个最优的结果,因此应该运用剪枝,当目前搜索的情况已经比记录的最优情况要差的时候,此时可以结束本次深搜了;

对于每个小猫咪有两种可能的结果,第一个就是将它放在已经付钱的缆车上(在承重允许的情况下),第二个就是为它新开一辆缆车;
剪枝是在深搜的开始,除了递归边界(搜索所有的小猫),还可以增加一个剪枝,也即刚刚提及的,若当前搜索记录的缆车数目已经比最优结果差了,此时不需要再继续搜索下去,直接返回就好了

关于本题的搜索顺序问题,在本题中若优先搜索重量大的小猫,可以减少搜索的分支数目(每个小猫都给了它独开一辆缆车的权利,但事实上是重量大的小猫独占一个缆车的可能性最大,而且最优结果也的确会是这样);在搜索时先按照这样的搜索顺序,找到最小count的可能性最大,有利于后续的剪枝,因此减少搜索的分支数目

#include<bits/stdc++.h>
using namespace std;

const int N = 20;
int w[N], car[N];
int W, n;
int ans;

bool cmp(int a, int b)
{
    return a > b;
}
//index 为当前考虑的小猫的编号,cnt为目前已经花钱购买的缆车数量
void dfs(int index, int cnt)
{
    //当前在第0辆缆车上,这里初始化为0,默认一上来是没有租任何缆车的
    if(cnt >= ans)  return;
    if(index == n + 1){
        ans = min(ans, cnt);
        return;
    }

    //看之前的缆车能否放下
    for(int i = 1;i <= cnt;i ++){
        // 遍历每个缆车
        if(w[index] <= car[i]){
            //可以放入
            car[i] -= w[index];
            dfs(index + 1, cnt);
            car[i] += w[index];
        }
    }
    //开个新的缆车,贪心在这里不一定适用?不一定将小的全放进去就是最优方案,有可能搭配起来反而利用空间最大?
    car[cnt + 1] -= w[index];
    dfs(index + 1, cnt + 1);
    car[cnt + 1] += w[index];   //注意DFS一定要恢复现场,就是一条路走到头之后要回退到最后一个节点的母节点,再走一条新的路走到底;所以对于每条路深搜完都要进行恢复现场!!
}
int main()
{
    scanf("%d%d", &n, &W);
    for(int i = 1;i <= n;i ++) {
        cin >> w[i];
        car[i] = W;
    }
    sort(w + 1, w + 1 + n, cmp);
    ans = n;  //最坏情况下一只猫一个缆车
    dfs(1, 0);
    cout << ans << endl;
    return 0;
}

感觉对DFS还是有些不熟练;第一个不太知道什么时候用深搜,一个不太理解DFS的原理
本题中的DFS就像一颗决策树一样,用于枚举所有可能的情况(暴力搜索本来就是);对于决策树的每一个节点都有不同的情况,这就是不同情况下的树的生成
这里其实在不同节点(不同小猫)选用不同的处理方式,然后递归,然后恢复现场


166. 数独

数独是一种传统益智游戏,你需要把一个 9×9的数独补充完整,使得数独中每行、每列、每个 3×3
的九宫格内数字 1∼9均恰好出现一次。
请编写一个程序填写数独。
输入格式
输入包含多组测试用例。
每个测试用例占一行,包含 81个字符,代表数独的 81个格内数据(顺序总体由上到下,同行由左到右)。
每个字符都是一个数字(1−9)或一个 .(表示尚未填充)。
您可以假设输入中的每个谜题都只有一个解决方案。
文件结尾处为包含单词 end 的单行,表示输入结束。
输出格式
每个测试用例,输出一行数据,代表填充完全后的数独。
输入样例:
4…8.5.3…7…2…6…8.4…1…6.3.7.5…2…1.4…
…52…8.4…3…9…5.1…6…2…7…3…6…1…7.4…3.
end
输出样例:
417369825632158947958724316825437169791586432346912758289643571573291684164875293
416837529982465371735129468571298643293746185864351297647913852359682714128574936

自己写的版本(超时了,样例都跑不出来)

#include<bits/stdc++.h>
using namespace std;

const int N = 15;
int g[N][N];
bool l[N][N], c[N][N], sq[N][N];

//判断属于哪个小方块
int judge(int x, int y)
{
    if(x < 3){
        if(y < 3) return 0;
        if(y < 6) return 1;
        else return 2;
    }
    if(x < 6){
        if(y < 3) return 3;
        if(y < 6) return 4;
        else return 5;
    }
    else{
        if(y < 3) return 6;
        if(y < 6) return 7;
        else return 8;
    }
}

void dfs(int x, int y, int cnt)
{
    if(cnt == 81){
        return;
        for(int i = 0;i < 9;i ++)
            for(int j = 0;j <9;j ++)
                printf("%d", g[i][j]);

        return;
    }

    for(int i = x;i < 9;i ++){
        for(int j = 0;j < 9;j ++){
            if(g[i][j] == 0){
                int z = judge(i, j);
                //需要填写数独的位置
                for(int k = 1;k <= 9;k ++){
                    if(!l[i][k] && !c[j][k] && !sq[z][k]){
                        //这个数字可以被填入
                        g[i][j] = k;
                        dfs(i, j, cnt + 1);
                        g[i][j] = 0;
                    }
                }
            }
        }
    }
}
int main()
{
    string str;
    int cnt = 0;
    while(cin >> str){
        if(str != "end"){
            memset(g, 0, sizeof g);
            for(int i = 0;i < 81;i ++){
                if(str[i] != '.'){
                    cnt ++;
                    int x = i / 9;
                    int y = i % 9;
                    int z = judge(x, y);
                    int k = str[i] - '0';
                    g[x][y] = k;
                    l[x][k] = 1; c[y][k] = 1; sq[z][k] = 1;
                }
            }
            dfs(0, 0, cnt);
        }
    }
    return 0;
}

做深搜题目的思路应该是:

  1. 思考搜索是否可解,如何搜索;本题的做法其实也很常规,自己写的做法也想到了,就是对每个没有填数的位置进行可以填写的数字遍历,此时在题设保证有唯一解的情况下一定可以得到答案
  2. 优化搜索,避免TLE
    • 优化搜索顺序:大部分情况下我们应该选择分支较少的节点(先搜索)
    • 排除等效冗余
    • 可行性剪枝(本题是考察可行性)
    • 最优化剪枝(前一题)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值