寒假:Day20

Day20

今天把搜索专题弄完,准备进入图论部分!

166. 数独 - AcWing题库

利用数字的二进制来枚举每一个格子都能填哪些数,先将每一行、每一列、每一个九宫格都初始化成二进制的 111111111 111111111 111111111 也就是十进制的511,然后如果要用到某一个数字 i i i ,那就把二进制上的那一位1变成0,也就是十进制减去 2 i 2^i 2i 即可。利用二进制来枚举表示状态,妙哉!

#include <iostream>
#include <algorithm>
using namespace std;
const int N = 9;

int ones[1 << N], map[1 << N]; // ones存的是每个数的二进制有多少个1,map存2的次方数的幂
int row[N], col[N], cell[3][3]; // 利用二进制来记录行、列、九宫格内都还能填什么
char str[100];

inline int lowbit(int x) // 返回末尾1,inline可以省去调用函数的时间
{
    return x & -x;
}

void init() // 初始化,将每一个都初始化成511,也就是二进制的111111111,表示一开始所有数都能填
{
    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;
}

// 求可选方案的交集
inline int get(int x, int y) // 三个限制条件进行与运算求出交集
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}

bool dfs(int cnt) // cnt表示还有多少个空位
{
    if (!cnt) return true; // 如果已经放满了,那就返回真即可
    // 每次找出可选方案数最少的空格,先遍历选择少的格子能优化时间
    int minv = 10;
    int x, y; // 下标
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            if (str[i * 9 + j] == '.') // i * 9 + j是二维变一维的转换,如果是未填数的格子
            {
                int t = ones[get(i, j)]; // t表示[i, j] 这个格子能填多少数字
                if (t < minv)
                {
                    minv = t; // 更新
                    x = i, y = j;
                }
            }
    for (int i = get(x, y); i; i -= lowbit(i)) // 枚举二进制上的1
    {
        int t = map[lowbit(i)];
        // 修改状态
        row[x] -= 1 << t;
        col[y] -= 1 << t;
        cell[x / 3][y / 3] -= 1 << t;
        str[x * 9 + y] = '1' + t; // 要记得放入数字
        if (dfs(cnt - 1)) return true; // 递归下一层
        // 恢复现场
        row[x] += 1 << t;
        col[y] += 1 << t;
        cell[x / 3][y / 3] += 1 << t;
        str[x * 9 + y] = '.';
    }
    return false; // 返回假,说明这个方案不对
}

int main()
{
    for (int i = 0; i < N; i ++ ) map[1 << i] = i; // 预处理出2的次方数的幂
    
    for (int i = 0; i < 1 << N; i ++ ) // 从1到512,预处理出每一个数二进制下1的个数
    {
        int s = 0;
        for (int j = i; j; j -= lowbit(j)) s ++ ; // 利用lowbit来求二进制中1的个数
        ones[i] = s; // i的二进制表示中有s个1
    }
    
    while (cin >> str, str[0] != 'e')
    {
        init();
        int cnt = 0; // 记录有多少个空位
        for (int i = 0, k = 0; i < N; i ++ ) // i表示行数
            for (int j = 0; j < N; j ++ , k ++ ) // j表示列数
                if (str[k] != '.') // 如果说这个格子上本身就有数字
                {
                    int t = str[k] - '1';
                    row[i] -= 1 << t; // 相对应的行、列、九宫格都要减去二进制数
                    col[j] -= 1 << t;
                    cell[i / 3][j / 3] -= 1 << t;
                }
                else cnt ++ ;
        dfs(cnt);
        cout << str << endl; 
    }
    return 0;
}	

167. 木棒 - AcWing题库

训练剪枝的经典题,一般的剪枝从一下几个方面考虑优化:

  1. 优化搜索顺序,大部分情况下我们优先搜索分支较少的点
  2. 排除等效冗余情况,不需要重复某一个相同的方案
  3. 可行性剪枝,通过分析题目条件,遇到错误方案立马返回
  4. 最优性剪枝,如果发现当前方案不可能是最优解了,立马返回
#include<bits/stdc++.h>
using namespace std;

const int N = 70;

int n;
int w[N], sum, length; // sum表示木棒长度总和,length表示每根木棍的最小长度
bool st[N];

bool dfs(int u, int s, int start) // u表示木棍编号,s表示当前木棍的长度,start表示木棒编号
{
    if (u * length == sum) return true; // 如果方案成立,返回真
    if (s == length) return dfs(u + 1, 0, 0); // 如果当前木棍已经到达长度,则递归下一根
    for (int i = start; i < n; i++) 
    {
        if (st[i]) continue; // 如果这根木棒已经用过,则继续
        if (s + w[i] > length) continue; // 如果加上这根木棒大于length,则继续
        
        st[i] = true; // 当前木棒标记成已用过
        if (dfs(u, s + w[i], i + 1)) return true; // 递归下一层
        st[i] = false; // 恢复现场
        
        if (!s) return false; // 如果第一根就放不进去,则整个方案错误,返回假
    
        if (s + w[i] == length) return false; // 如果最后一根放进去了,整个方案错误,返回假
        
        int j = i;
        while (j < n && w[j] == w[i]) j++; // 接下来相同长度的木棒不需要重复递归
        i = j - 1;
    }
    return false;
}
int main()
{
    while (cin >> n, n)
    {
        sum = 0;
        memset(st, 0, sizeof st);
        for (int i = 0; i < n; i++) {
            cin >> w[i];
            sum += w[i];
        }
        
        sort(w, w + n);
        reverse(w, w + n); // 从大到小排序
        
        length = 1; // 从1开始搜
        while (1)
        {
            if (sum % length == 0 && dfs(0, 0, 0)) // 找到合法方案
            {
                cout << length << endl ;
                break;
            }
            length ++;
        }
    }
    return 0;
}

168. 生日蛋糕 - AcWing题库

这道题偏向数学方面的一些剪枝,搜索不难,剪枝难,尽可能把能剪得都剪掉,防止超时!

#include<bits/stdc++.h>
using namespace std;
const int N = 25, INF = 1e9;
int n, m;
int minv[N], mins[N]; // 预处理每一层的最小体积和最小表面积
int R[N], H[N]; // 求得的当前层的半价和表面积
int ans = INF;

void dfs(int u, int v, int s) // u是到第几层了,v是当前总体积,s是当前总表面积
{
    if (v + minv[u] > n) return; // 如果体积超过给定体积了,返回
    if (s + mins[u] >= ans) return; // 如果表面积超过目前答案了,返回
    if (s + 2 * (n - v) / R[u + 1] >= ans) return;
    
    if (!u) // 如果到最后一层了
    {
        if (v == n) ans = s; // 如果体积相等,那么更新答案
        return;
    }
    // 双循环枚举下一层半价和高度,继续深搜
    for (int r = min(R[u + 1] - 1, (int)sqrt(n - v)); r >= u; r--) 
        for (int h = min(H[u + 1] - 1, (n - v) / r / r); h >= u; h--)
        {
            int t = 0;
            if (u == m) t = r * r; // 如果是第一层,需要加上上表面积,也就是一个圆
            R[u] = r, H[u] = h; // 更新高度和半价
            dfs(u - 1, v + r * r * h, s + 2 * r * h + t); // 递归下一层
        }
}

int main()
{
    cin >> n >> m;
    
    for (int i = 1; i <= m; i++) // 先预处理出每一层的最小体积和表面积
    {
        minv[i] = minv[i - 1] + i * i * i;
        mins[i] = mins[i - 1] + 2 * i * i;
    }
    R[m + 1] = H[m + 1] = INF;
    dfs(m, 0, 0);
    if (ans == INF) ans = 0;
    cout << ans << endl ;
    
    return 0;
}

170. 加成序列 - AcWing题库

采取迭代加深的做法。一层一层的去搜答案序列

#include<bits/stdc++.h>
using namespace std;
const int N = 110;

int n;
int path[N]; // path记录路径

bool dfs(int u, int depth) // u表示填了第几个数了,depth表示路径长度
{
    if (u > depth) return false;
    if (path[u - 1] == n) return true; // 如果走到终点了,返回true
    
    bool st[N] = {0};
    for (int i = u - 1; i >= 0; i--) // 从后向前枚举
        for (int j = i; j >= 0; j--)
        {
            int s = path[i] + path[j]; // 枚举可能情况
            if (s > n || s <= path[u - 1] || st[s]) continue; // 不合法就继续下一个
            
            st[s] = true; // 标记为已用过
            path[u] = s; // 填入数字
            if (dfs(u + 1, depth)) return true; // 递归下一层
        }
    return false;
}

int main(void)
{
    path[0] = 1;
    while (cin >> n, n)
    {
        int depth = 1;
        while (!dfs(1, depth)) depth++; // 如果当前路径长度不行,路径长度加1继续搜
        for (int i = 0; i < depth; i++) cout << path[i] << " ";
        cout << endl;
    }
    return 0;
}

171. 送礼物 - AcWing题库

一道蕴含空间换时间思想的经典题目,利用分段枚举将前半部分预处理出来,然后枚举后半部分的时候可以直接调用前半部分的信息,将 2 N 2^N 2N 优化成 2 K + 2 ( N − K ) ∗ l o g ( N − K ) 2^K + 2^(N - K) * log(N - K) 2K+2(NK)log(NK) ,利用额外空间来优化时间,这也是道经典的双向DFS例题

#include<bits/stdc++.h>
using namespace std;
const int N = 46;
using LL = long long;

int n, m, k;
int w[N];
int weights[1 << 25], cnt = 1; // 利用二进制来枚举拿不拿当前礼物的情况,cnt为下标从1开始
int ans;

void dfs1(int u, int s) //u表示礼物编号,s表示礼物总重量
{
    if (u == k) // 枚举到第k个礼物
    {
        weights[cnt ++] = s;
        return ;
    }
    dfs1(u + 1, s); // 不拿当前物品,枚举下一层
    if ((LL)s + w[u] <= m) dfs1(u + 1, s + w[u]); // 拿上当前礼物,枚举下一层
}

void dfs2(int u, int s) // u表示枚举到第u个礼物,s表示当前
{
    if (u == n) // 走到第n层
    {
        int l = 0, r = cnt - 1; // 二分找到第一个小于等于 m - s的方案
        while (l < r)
        {
            int mid = l + r + 1 >> 1;
            if (weights[mid] <= m - s) l = mid;
            else r = mid - 1;
        }
        ans = max(ans, weights[l] + s); // 更新答案
        return;
    }
    
    dfs2(u + 1, s); // 不拿第u个礼物,递归下一个
    if ((LL)s + w[u] <= m) dfs2(u + 1, s + w[u]); // 拿第u个礼物,递归下一个
}

int main(void)
{
    cin >> m >> n;
    for (int i = 0; i < n; i++) cin >> w[i];
    
    sort(w, w + n);
    reverse(w, w + n); // 从大到小排列礼物重量
    
    k = n / 2 + 2; // k就是分段枚举
    dfs1(0, 0); // 深搜预处理前半段的信息
    
    sort(weights, weights + cnt);
    cnt = unique(weights, weights + cnt) - weights; // 排序 + 去重
    
    dfs2(k, 0); // 深搜更新答案
    
    cout << ans << endl ;
    return 0;
}

180. 排书 - AcWing题库

利用后继关系来表示书籍的排列顺序是个很巧妙的方法,深搜不难,难在如何用一个巧妙的方法取枚举你当前搜索的状态,还有就是如何剪枝来优化你的搜索时间。

#include<bits/stdc++.h>
using namespace std;
const int N = 15;

int n;
int q[N];
int w[5][N]; // 记录每一层的情况

int f() // 求当前排序有多少个错误的后继关系,除三上取整表示至少还需要多少次才能恢复
{
    int tot = 0;
    for (int i = 0; i < n - 1; i++)
        if (q[i + 1] != q[i] + 1) tot++;
    return (tot + 2) / 3;
}

bool dfs(int depth, int max_depth) // depth表示这种方案的操作数,max_depth表示答案
{
    if (depth + f() > max_depth) return false; // 当前方案已经不可能了,返回假
    if (f() == 0) return true; // 已经排好了,返回真
    
    for (int len = 1; len <= n; len++) // 枚举需要移动的连续书籍长度
        for (int l = 0; l + len - 1 < n; l++) // 枚举左端点
        {
            int r = l + len - 1; // 求出右端点
            for (int k = r + 1; k < n; k++) // 枚举插入的点
            {
                memcpy(w[depth], q, sizeof q); // 把当前的后继关系存一份
                int y = l;
                for (int x = r + 1; x <= k; x ++, y++) q[y] =w[depth][x]; // 更新后继关系
                for (int x = l; x <= r; x++, y++) q[y] = w[depth][x]; // 更新后继关系
                if (dfs(depth + 1, max_depth)) return true; // 递归下一层
                memcpy(q, w[depth], sizeof q); // 恢复现场
            }
        }
    return false;
}

int main(void)
{
    int t;
    cin >> t;
    while (t--)
    {
        cin >> n;
        for (int i = 0; i < n; i++) cin >> q[i];
        
        int depth = 0; // 初始认为一次都不用
        while (depth < 5 && !dfs(0, depth)) depth ++; // 如果当前的答案不行,答案加1继续搜

        if (depth >= 5) puts("5 or more");
        else cout << depth << endl;
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值