DFS剪枝优化 小猫爬山 数独

DFS剪枝策略总结 

  1. 优化搜索顺序 优先搜索分支数少(剩余选择少)的情况 
  2. 排除等效冗余 若对顺序没有要求 可以将排列转化为组合
  3. 可行性剪枝 不合法的情况不进行搜索
  4. 最优化剪枝 若当前的"消耗"已经超过暂存的答案 直接返回
  5. 记忆化搜索(DP)

AcWing 165.小猫爬山

翰翰和达达饲养了 N 只小猫 这天 小猫们要去爬山

经历了千辛万苦 小猫们终于爬上了山顶 但是疲倦的它们再也不想徒步走下山了

翰翰和达达只好花钱让它们坐索道下山

索道上的缆车最大承重量为 W 而 N 只小猫的重量分别是 C1、C2……CN

每辆缆车上的小猫的重量之和不能超过W

每租用一辆缆车 翰翰和达达就要付 美元 所以他们想知道

最少需要付多少美元才能把这N只小猫都运送下山?

输入格式

第 1 行:包含两个用空格隔开的整数 N 和 W

第 2~N+1 行:每行一个整数 其中第 i+1 行的整数表示第 i 只小猫的重量 Ci

输出格式

输出一个整数,表示最少需要多少美元,也就是最少需要多少辆缆车。

数据范围

1≤N≤18
1≤Ci≤W≤10^8

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 20;
int w[N], sum[N];//小猫重量和每辆车上当前的载重
int n, limit, ans;

//考虑到第u只猫 已经用了k辆车
void dfs(int u, int k)
{
    //DFS剪枝优化(最优性剪纸)
    //当前情况已经比在暂存答案更差了 就不用继续搜了
    if (k >= ans)return;

    //搜到了一个更优的方案 更新答案
    if (u == n)ans = k;

    //当前有k辆车 有k种放置选择
    for (int i = 0; i < k; i++)
    {
        //DFS可行性剪枝
        //必须满足条件才能放入
        if (sum[i] + w[u] <= limit)
        {
            //放入猫猫
            sum[i] += w[u];
            dfs(u + 1, k);
            //恢复现场
            sum[i] -= w[u];
        }
    }
    //新开一辆车也是一种选择
    //k从0开始 sum[k]其实是指第k+1辆车
    sum[k] = w[u];
    dfs(u + 1, k + 1);
    sum[k]=0;//恢复现场
}

int main()
{
    cin >> n >> limit;
    ans = n;
    for (int i = 0; i < n; i++)cin >> w[i];

    //DFS优化搜索顺序
    //先搜索分支少的
    //先放入重猫 这样剩下来的选择更少
    sort(w, w + n);
    reverse(w, w + n);

    dfs(0, 0);

    cout << ans << endl;

    return 0;
}

AcWing 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<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 9;
const int M = 1 << N;
//状态压缩 行 列 九宫格 用二进制数表示状态
int row[N], col[N], cell[3][3];
char str[N*N+10];//存图
//求二进制数中有多少个1 
//求以2为底的对数
int ones[M], map[M];

//最开始什么数都没填入
//所有二进制位都为1 表示可以填
void init()
{
    //二进制位全部设置位1
    //二进制的第0位到第8位表示1-9
    for (int i = 0; i < N; i++)row[i] = col[i] = (1 << N) - 1;
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            cell[i][j] = (1 << N) - 1;
}

//在(x,y)填上t
//把(x,y)这个位置的数删掉
void draw(int x, int y, int t, bool is_set)
{
    //is_set==true 表示填入
    //二维坐标压缩到一维(注意要从0)开始
    //t是0-8
    //因为初始化的时候 二进制第0位设为1
    if (is_set)str[x * N + y] = '1' + t;
    else str[x * N + y] = '.';

    //获取填入t时对应修改的二进制位
    int v = 1 << t;
    if (!is_set)v = -v;//删除时要减去
    row[x] -= v;
    col[y] -= v;
    cell[x / 3][y / 3] -= v;
}

int lowbit(int x)
{
    return x & (-x);
}

//分析(x,y)填入哪些数是合法的
int get(int x, int y)
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}

bool dfs(int cnt)
{
    //没有剩余的空格了 意味着填完了
    if (!cnt)return true;

    //寻找可以填的数(的情况)最少的的空格
    //DFS的搜索顺序优化
    //希望刚开始搜索时的分支数目尽可能少
    int minv = 10;
    int x, y;
    for(int i=0;i<N;i++)
        for(int j=0;j<N;j++)
            if (str[i * N + j] == '.')//二维映射到一维
            {
                int state = get(i, j);
                if (ones[state] < minv)
                {
                    minv = ones[state];
                    x = i, y = j;
                }
            }
    int state = get(x, y);
    for (int i = state; i; i -= lowbit(i))
    {
        //lowbit(i)获取最低位的1
        //i-=lowbit(i)相当于将最低位的1改为0
        //lowbit(i)返回的是2的某次方 次方数对应填写的数字
        int t = map[lowbit(i)];
        draw(x, y, t, true);
        if (dfs(cnt - 1))return true;
        draw(x, y, t, false);//恢复现场
    }

    //DFS函数最后通常会有一个return false
    //当该层的搜索循环迭代完都没有return true的机会
    //说明该层的所有情况都不行了
    //应当返回到上一层 撤销这次错误的尝试(恢复现场)
    //return true 一般在开头
    return false;
}

int main()
{
    //求以2为底的对数
    for (int i = 0; i < N; i++)map[1 << i] = i;
    //求二进制数中有多少个1
    for (int i = 0; i < 1 << N; i++)
        for (int j = 0; j < N; j++)
            ones[i] += (i >> j) & 1;

    while (cin >> str, str[0] != 'e')
    {
        init();

        //cnt表示有多少个空位
        int cnt = 0;
        //把一行字符串映射到9X9的二维数组
        for (int i = 0, k = 0; i < N; i++)
            for (int j = 0; j < N; j++, k++)
                if (str[k] != '.')
                {
                    //t是0-8
                    //因为初始化的时候 二进制第0位设为1
                    int t = str[k] - '1';
                    //将字符覆盖为数字
                    draw(i, j, t, true);
                }
                else cnt++;

        dfs(cnt);

        puts(str);
    }

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

没伞的男孩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值