首页 > 其他分享 >DFS 剪枝

DFS 剪枝

时间:2023-10-21 09:11:33浏览次数:34  
标签:剪枝 return int rint dfs DFS ans

DFS

剪枝

\(DFS\) 是一种常见的算法,大部分情况下,很少会爆搜为正解的题目。因为 \(DFS\) 的时间复杂度特别高。

我们可以先写一段 dfs 的伪代码

int ans = 最坏情况,  now;  // now 为当前答案

void dfs(传入数值) 
{
    if (到达目的地) 
    {
        ans = 从当前解与已有解中选最优;
    }
    for (遍历所有可能性)
    {
        if (可行) 
        {
            进行操作;
            dfs(缩小规模);
            撤回操作;
        }        
    }
}

记忆化搜索

因为在搜索中,相同的传入值往往会带来相同的解,那我们就可以用数组来记忆

int g[N];  // 定义记忆化数组
int ans = 最坏情况, now;

void dfs(传入数值) 
{
    if (g[规模] != 无效数值) return;  // 或记录解,视情况而定
    if (到达目的地) ans = 从当前解与已有解中选最优;  // 输出解,视情况而定
    for (遍历所有可能性)
    {
         if (可行) 
        {
            进行操作;
            dfs(缩小规模);
            撤回操作;
        }       
    }
}

int main() 
{
    // ...
    memset(g, 无效数值, sizeof(g));  // 初始化记忆化数组
    // ...
}

最优性剪枝

在搜索中导致运行慢的原因还有一种,就是在当前解已经比已有解差时仍然在搜索,那么我们只需要判断一下当前解是否已经差于已有解。

int ans = 最坏情况, now;

void dfs(传入数值) 
{
    if (now比ans的答案还要差) return;
    if (到达目的地) ans = 从当前解与已有解中选最优;
    for (遍历所有可能性)
    {
        if (可行) 
        {
            进行操作;
            dfs(缩小规模);
            撤回操作;
        }        
    }
}

可行性剪枝

在搜索过程中当前解已经不可用了还继续搜索下去也是运行慢的原因。

int ans = 最坏情况, now;

void dfs(传入数值) 
{
    if (当前解已不可用) return;
    if (到达目的地) ans = 从当前解与已有解中选最优;
    for (遍历所有可能性)
    {
        if (可行) 
        {
            进行操作;
            dfs(缩小规模);
            撤回操作;
        }
    }
}

剪枝思路

剪枝思路有很多种,大多需要对于具体问题来分析,在此简要介绍几种常见的剪枝思路。

  • 极端法:考虑极端情况,如果最极端(最理想)的情况都无法满足,那么肯定实际情况搜出来的结果不会更优了。

  • 调整法:通过对子树的比较剪掉重复子树和明显不是最有「前途」的子树。

  • 数学方法:比如在图论中借助连通分量,数论中借助模方程的分析,借助不等式的放缩来估计下界等等。

例题

[NOIP2002 普及组] 选数

题目传送门
此题并不需要剪枝,注释代码

#include <bits/stdc++.h>

#define int long long
#define rint register int
#define endl '\n'

using namespace std;

const int N = 2e1 + 5;

int n, k;
int a[N];
int ans;

bool isprime(int p)
{
    for (rint i = 2; i * i <= p; i++)
    {
        if (p % i == 0)
        {
            return false;
        }
    }
    return true;
}

void dfs(int cnt, int sum, int start)
//cnt 代表现在选择了多少个数
//sum 表示当前的和
//start 表示从第几个数开始
{
    if (cnt == k)
    {
        if (isprime(sum))
        {
            ans++;
        }
        return ;
    }
    
    for (rint i = start; i <= n; i++)
    {
        dfs(cnt + 1, sum + a[i], i + 1);
    }
}

signed main()
{
    cin >> n >> k;
    
    for (rint i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    
    dfs(0, 0 ,1);
    
    cout << ans << endl;
    
    return 0;
}

小木棍

题目传送门
非常经典的剪枝题。

  • 可行性剪枝:记录上一次失败的值, 如果这次还是的话, 那么肯定是不能选择的
  • 最优性剪枝:比 maxx 小的一定不是最优,所以从 maxx 开始搜索

其他剪枝见注释代码

#include <bits/stdc++.h>

#define rint register int
#define endl '\n'

using namespace std;

const int N = 1e2 + 5;

int n;
int a[N];
bool v[N];
int len, cnt;
int sum;
int maxx;

bool dfs(int x, int now, int last)
// x 为当前第几段了
//now 记录当前段的长度
//last 为上一次已经选过的值
{
    if (x > cnt)//如果当前搜到的段数已经在 cnt 之后,说明可行
    {
        return 1;
    }
    
    if (now == len)//如果长度刚刚好,直接下一次搜索
    {
        return dfs(x + 1, 0, 1);
    }

    int fail = 0; 
    //记录上一次失败的值,如果这次还是的话,那么肯定是不能选择的
    for (rint i = last; i <= n; i++)
    {
        if (!v[i] && now + a[i] <= len && fail != a[i])
        {
            v[i] = 1;
            if (dfs(x, now + a[i], i + 1)) 
            {
                return 1;
            }
            fail = a[i];
            v[i] = 0;
            if (now == 0 || now + a[i] == len)
            {
                return 0;
            }
        }
    }

    return 0;
}

int main()
{
    cin >> n;
    
    for (rint i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum += a[i];
        maxx = max(maxx, a[i]);
        //找出最大值和所有数的和
    }

    sort(a + 1, a + n + 1);
    reverse(a + 1, a + n + 1);//从大到小排序

    for (len = maxx; len < sum; len++)
    //比maxx小的一定不是最优,所以从maxx开始搜索
    {
        if (sum % len)
        //一定分不出来
        {
            continue;
        }
        cnt = sum / len;
        memset(v, 0, sizeof v);
        if (dfs(1, 0, 1))
        {
            break;
        }
    }

    cout << len << endl;

    return 0;
}

[NOI1999] 生日蛋糕

题目传送门

此题更多的是数学上的剪枝 + 贪心

#include <bits/stdc++.h>

#define rint register int
#define endl '\n'

using namespace std;

const int N = 1e6 + 5;
const int inf = 1e9;

int n, m;
int ans = inf;
int mins[N], minv[N];

void dfs(int c, int v, int s, int h, int r) 
//c 层数, v 体积, s 抹奶油的外表面积 , r 半径, h 高度
{
    if(c == 0)
    {
    	if(v == n) 
    	{
    	    ans = min(ans ,s);
    	}
    	return;
	}
	
    if(v + minv[c] > n) return;
    /*
    当前的体积 > n    return
    */
    if(s + mins[c] > ans) return;
    if(s + 2 * (n - v) / r > ans) return;
    /*
    当前的奶油面积 + 之后的奶油面积 > 现在已求出的的最小奶油面积  return
    */
    
    for (rint i = r - 1; i >= c; i--)
    { 
    	if(c == m)
    	{
    	    s = i * i;
    	}
    	int Maxh = min(h - 1, (n - v - minv[c - 1]) / (i * i)); 
        for (rint j = Maxh; j >= c; j--)
        {
            dfs(c - 1, v + i * i * j, s + 2 * i * j, j, i);            
        }
    }
}

signed main() 
{
    cin >> n >> m;
    
	int MaxR = sqrt(n);
	
	for(int i = 1; i <= n; i++)
	{
		minv[i] = minv[i-1] + i * i * i;
		mins[i] = mins[i-1] + 2 * i * i;
	} 
	
	dfs(m, 0, 0, n, MaxR); 
	
    if(ans == inf)
    {
        ans = 0;
    }
    
    cout << ans << endl;
    
    return 0;
}

AcWing.165 小猫爬山

题目传送门

#include <bits/stdc++.h>

#define rint register int
#define endl '\n'
#define int long long

using namespace std;

const int N = 2e1 + 5;
const int inf = 1e18;

int n, w;
int a[N];
int res = inf;
int p[N];
//p[i] 表示的是第 i 个车已经装了重量为 p[i] 的猫

void dfs(int x, int cnt)
//该拿第 x 只猫了
//累计用了 cnt 个车
{
    if (cnt >= res)
    {
        return ;
    }
    
    if (x == n + 1)
    {
        res = min(res, cnt);
        return ;
    }
    
    for (rint i = 1; i <= cnt; i++)
    {
        if (a[x] <= w - p[i])
        //如果还能放小猫
        {
            p[i] += a[x];
            dfs(x + 1, cnt);
            p[i] -= a[x];//恢复现场
        }
    }
    
    p[++cnt] = a[x];
    
    dfs(x + 1, cnt);
    
    p[cnt] = 0;//恢复现场
}

signed main()
{
    cin >> n >> w;
    
    for (rint i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    
    sort(a + 1, a + n + 1);
    reverse(a + 1, a + n + 1);
    
    dfs(1, 0);
    
    cout << res << endl;
    
    return 0;
}

标签:剪枝,return,int,rint,dfs,DFS,ans
From: https://www.cnblogs.com/spaceswalker/p/17778446.html

相关文章

  • VM部署HDFS集群
    上传hadoop-3.3.4.tar.gz到/export/server解压tar-zxvfhadoop-3.3.4.tar.gz-C/export/server/#快捷方式ln-s/export/server/hadoop-3.3.4hadoopHadoop安装包目录结构目录说明bin存放Hadoop的各类程序(命令)etc存放Hadoop的配置文件sbin管理员程序(s......
  • 最优性剪枝,可行性剪枝,优化搜索顺序,排除等效冗余
    杨辉三角:  //https://www.luogu.com.cn/problem/P1118//最优性剪枝://由高中知识可得,abcd四个数符合杨辉三角的数相乘,即//res=a+3*b+3*c+d,前面的常数项也就是杨辉三角的数字//根据此结论,进行剪枝//由于暴力枚举全排列+部分剪枝不可过,所以要考虑方法性剪枝......
  • 【DFS】129. 求根节点到叶子结点的和
    链接https://leetcode.cn/problems/sum-root-to-leaf-numbers/description/思路时刻记住,DFS是递归的一种。而解决递归,最朴素的思路就是:递归的定义就是递归的解。题目要求我们求根节点到叶子结点的和,我们要提供一个值保持其状态,退出条件直接用题目给的限定即可。代码#Defin......
  • P9233 [蓝桥杯 2023 省 A] 颜色平衡树 (dfs序 莫队)
    P9233[蓝桥杯2023省A]颜色平衡树(dfs序莫队)莫队原理:https://zhuanlan.zhihu.com/p/115243708对于树上的每个结点,按照dfs序打上时间戳,这样就可以把每一个结点对应的子树的答案转化为一个区间的答案。将子树询问离线下来变成\(n\)个区间查询。用\(cnt\)数组维护颜色......
  • FastDFS+Nginx - 本地搭建文件服务器同时实现在外远程访问「端口映射」 转载
    前言FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡......
  • 【LeetCode递归】括号生成,使用dfs
    括号匹配数字n代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。示例1:输入:n=3输出:["((()))","(()())","(())()","()(())","()()()"]示例2:输入:n=1输出:["()"]提示:1<=n<=8代码与思路有效的括号组合要满足两个条件:①左右......
  • 力扣18:四数之和(双指针+剪枝)
    给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a],nums[b],nums[c],nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):0<=a,b,c,d <na、b、c 和 d 互不相同nums[a]+nums[b]......
  • JAVA图搜索算法之DFS-BFS
    图算法DFS与BFSBFS和DFS代表对图进行遍历,即搜索的算法,搜索算法中常用的只要有两种算法:深度优先遍历(Depth-First-Search:DFS)和广度优先遍历(Breadth-First-Search:BFS)。一个图结构可以用来表示大量现实生活中的问题,比如,道路网络,计算机网络,社交网络,用户身份解析图①DFS......
  • FastDFS+Nginx,轻轻松松搭建一个本地文件服务器
    前言1.本地搭建FastDFS文件系统2.局域网测试访问FastDFS3.安装cpolar内网穿透4.配置公网访问地址5.固定公网地址6.测试访问固定二级子域名前言FastDFS是一个开源的轻量级分布式文件系统,它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决......
  • CentOS 7.9 FastDFS 设置开机自启动
    CentOS7.9FastDFS设置开机自启动  一、前言关于 FastDFS服务的启动、停止、重启相关脚本,可以参考如下博客:https://www.cnblogs.com/miracle-luna/p/17750542.html本文主要讲解如何使用systemctl系统命令,进行启动、停止、重启、查看FastDFS状态等操作。 二、......