九、DFS剪枝优化

那么在dfs剪枝优化之前,我们还需知道回溯的原理。(因为忘记提了)

什么时候要回溯呢?

外部搜索:改变整个大状态,需要撤销上一步对量的改变。

内部搜索:在同一个状态内改变,比如搜路径。

剪枝操作有什么呢?

删除冗余,极大减少时间复杂度。

1、优化搜索顺序         

在大部分情况下,我们应该优先搜索分支较小的节点。

什么意思呢?在构造递归搜索树的时候,要尽心类似贪心的思维,比如说在讨论背包问题时(比如接下来的小猫爬山),如果我从小到大或者任意放置,会导致接下来的分支剧增,如果从最大开始,很多的书都会放不进去,保证了局部最优,有利于求解。

2、排除等效冗余

最经典的问题就是当是一个组合问题的时候,不要用排列问题求解,比如放书顺序1 2 3 和 3 2 1对于放满状态并无影响。

3、可行性剪枝

变量不合法。如果某位变量要越界。比如数位dp的位数越界的话要return

4、最优性剪枝

如果这个已经不会更优,比如要求背包里放满时书最少,放两本书已经满了和放两本书还不满,那么放两本书要么最后状态不合法,要么会放三本书以上已经不是最优解。

5、记忆化搜索(dp!!!)

165. 小猫爬山 - AcWing题库

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>

const int N = 1e6 + 10;
const int M = 2 * N;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;

int c[20];
bool st[20];
int n,w;
int ans = 18;
int sum[20];
//确定搜索方式:把猫放进框里
void dfs(int u,int cnt,int tl)//现在是哪个数,用了多少个数了,一共有tl组
{
    if(tl >= ans) return;
    if(cnt == n) 
    {
        ans = tl;
        return;
    }
    for(int i = 0; i < tl; i ++)
    {
        if(sum[i] + c[u] <= w)
        {
            sum[i] += c[u];
            dfs(u + 1,cnt + 1,tl);
            sum[i] -= c[u];//恢复现场
        }
    }
    
    sum[tl] += c[u];
    dfs(u + 1,cnt + 1,tl + 1);
    sum[tl] -= c[u];//恢复现场
}
void solve()
{
    cin >> n >> w;
    for(int i = 0; i < n; i ++) cin >> c[i];

    sort(c, c + n,[&](int a,int b){
        return a > b;
    });//优化搜索顺序
    dfs(0,0,1);

    cout << ans << endl;
}
int main()
{
    solve();



    system("pause");
    return 0;
}

166. 数独 - AcWing题库

思路:

1、可填数的要求要满足Sr \wedge Sl \wedge S cell(x / 3,y / 3),即在这个3 * 3的格子内要有1~9个数,行1~9个数,列1~9个数;

2、对于 x =  Sr \wedge Sl \wedge S cell(x / 3,y / 3)下的二进制数,进行lowbit的枚举优化,枚举到以后对row、lie、cell进行优化

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>

const int N = 9;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;

int ones[1 << N];//不用循环遍历这个数有多少个1,不然不用lowbit遍历不好找
int mp[1 << N];//map知道第几位是1 1 -> 1 10 -> 2 100 -> 3
int row[N],col[N],cell[3][3];
char str[100];
inline int lowbit(int x)
{
    return x & -x;
}
//求可选方案的交集
inline int get(int x,int y)
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}
bool dfs(int cnt)
{
    if(!cnt) return true;
    //第一步 找到可选方案数最少的格子
    int minn = 10;
    int x,y;
    for(int i = 0; i < N; i ++)
        for(int j = 0; j < N; j ++)
        {
             if(str[i * 9 + j] == '.')
             {
                int t = ones[get(i,j)];
                if(t < minn)
                {
                    minn = t;
                    x = i, y = j;
                }
             }
        }
    for(int i = get(x,y); i; i -= lowbit(i))
    {
        int t = mp[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;
}
void init()
{
    for(int i = 0; i < N; i ++) row[i] = col[i] = (1 << N) - 1;// 2进制里边9个1,表示最开始情况下全能放
    for(int i = 0; i < 3; i ++)
        for(int j = 0; j < 3; j ++)
            cell[i][j] = (1 << N) - 1;
}
void solve()
{
    for(int i = 0 ; i < N; i ++) mp[1 << i] = i;
    for(int i = 0; i < 1 << N; i ++)
    {
        int s = 0;
        for(int j = i; j; j-= lowbit(j)) s ++;
        ones[i] = s;//i的二进制表示种有s个1
    }
    while(cin >> str,str[0] != 'e')
    {
        init();
        //去掉string上的点(初始化全能选),去掉row col cell
        int cnt = 0;
        for(int i = 0,k = 0; i < N; i ++)
            for(int j = 0; j < N; j ++,k ++)
            {
                if(str[k] != '.')
                {
                    int t = str[k] - '1';//把1 ~ 9映射成0 ~ 8
                    row[i] -= 1 << t;
                    col[j] -= 1 << t;
                    cell[i / 3][j / 3] -= 1 << t;
                }
                else cnt ++;
            }
        dfs(cnt);//把cnt个没有填过的格子填满

        cout << str << endl;
    }
}
int main()
{
    solve();



    system("pause");
    return 0;
}

167. 木棒 - AcWing题库

 思路:

 剪枝1:对于所有木棍的总和sum来说,只枚举sum的因数,因为总木棍数一定是一个整数

 剪枝2:优化搜索顺序:从大到小枚举(优先枚举分支较小)

 剪枝3:排除等效冗余方法:

1、用组合方式枚举而不是排列方式枚举

2、如果是木棒的第一根失败了,一定失败。因为小段放下去以后不成立,那么以后的都不会成立(因为如果合适的话,在同一根木棒里是等价的,等长木棒交换完成后在第一行也是等价的)即(!s == 0)第一次木棍啥也没找到,寄

3、如果当前木棒放下以后失败了,则略过所有长度相同的木棒

4、如果某一根木棍放到最后失败了就一定失败(s + w[i] == length[i]) //这种情况按理说是可以的,但是在我搜的过程中没出现,说明失败了

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>

const int N = 70;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;

int n;
int w[N],sum,length;
bool st[N];
bool dfs(int u,int s,int start)
{
    if(u * length == sum) return true;
    if(s == length) return dfs(u + 1,0,0);
    //剪枝3-1:i从start开始枚举避免排列
    for(int i = start; i < n; i ++)
    {
        if(st[i]) continue;
        if(s + w[i] > length) continue; 

        st[i] = true;
        if(dfs(u ,s + w[i],i + 1)) return true;
        st[i] = false;
        //如果走到这里,说明上述情况已经失败了 
        
        //对于失败的情况,我们进行剪枝
        //剪枝3-2:如果放第一根的时候失败了,那么以后都不用放了
        if(!s) return false;
        //剪枝3-4:如果放最后一根的时候失败了,那么以后都不用放了
        if(s + w[i] == length) return false;
        int j = i;
        while(j < n && w[j] == w[i]) j ++;
        i = j - 1;
    }
    
    return false;
}
void solve()
{
    while(cin >> n,n)
    {
        memset(st,0,sizeof st);
        sum = 0;
        for(int i = 0; i < n; i ++) 
        {
            cin >> w[i];
            sum += w[i];
        }
        //优化搜索顺序
        sort(w,w + n);
        reverse(w,w + n);
        length = 1;
        while(true)
        {
            if(sum % length == 0 && dfs(0,0,0))
            {
                cout << length << endl;
                break;
            }
            length ++;
        }
    }
    


}
int main()
{
    solve();



    system("pause");
    return 0;
}

 168. 生日蛋糕 - AcWing题库

思路:

剪枝1:从底向上搜,从大到小来枚举R,H,并且优先枚举R,因为R作用于体积是平方倍

剪枝2:当前层的R的范围是 u\leqslant R(u)\leqslant min(R(u + 1) - 1,\sqrt{n - v})

最小值是因为从顶往下1~m按照递减的顺序递减下去以后,最小的结果是1--2--3--4--5----u,否则不会达到M层。最大值是上一层已经固定的值 - 1,同时由于体积的差值为 n - v \geqslant R^{2}H,则一定满足\sqrt{n - v} \geqslant R

同理可得当前层高度H的范围是u\leqslant H(u)\leqslant min(H(u + 1) - 1,\frac{n - v}{R^{2}})

剪枝3:预处理最小值minv,mins

v + minv(u) \leqslant n

s + mins(u) < ans

剪枝4:S1 + S2 + S3 ······ + Su = \sum_{k = 1}^{u}2RkHk = \tfrac{2}{Ru + 1} RkHR(k + 1) > 2 \tfrac{2}{Ru + 1} \sum_{k = 1}^{u}2Rk^{_{2}}Hk

\sum_{k = 1}^{u}2Rk^{_{2}}Hk = n - v

\sum_{k = 1}^{u}Sk> \frac{2 * ( n - v)}{R(u+1)}

#include<bits/stdc++.h>

using namespace std;

#define ll long long
#define PII pair<int, int>
#define PLL pair<ll, ll>

const int N = 25;
const int M = 2 * N;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
const double PI = acos(-1.0);
const double eps = 1e-8;

int n,m;
int minv[N],mins[N];
int R[N],H[N];
int ans = INF;
void dfs(int u,int v,int 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);
           
        }
    }
}
void solve()
{
    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) puts("0");
    else cout << ans << endl;
}
int main()
{
   
    solve();



    system("pause");
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值