搜索优化剪枝策略

2023-10-30

状态剪枝

如果将搜索的状态看作结点,状态之间的转移看作边,搜索的过程就构建出了一棵“搜搜树”。其实,这也是判定搜索题目的一个方法。
所谓“剪枝”,就是在搜索树上去掉一些枝杈不进行搜索,从而减少小时间消耗。搜索树的剪枝如下图所示。
在这里插入图片描述
常用的思考策略
1、优化搜索顺序
大多数情况下优先搜索分枝较少的结点
2、排除等效冗余
3、可行性剪枝
4、最优性剪枝
但需要指出的是,搜索题目的性质极其灵活,在应用时一定要具体问题具体分析,设计合适的剪枝策略,不必拘泥于所学。
例1:数的划分

【问题描述】
将整数n分成k份,且每份不能为空,问有多少种不同的分法。当n=7,k=3,下面三种分法被认为是相同的:1,1,5; 1,5,1; 5,1,1
【输入格式】
输入文件只有一行为两个整数n和k (6<n≤200,2≤k≤6)
【输出格式】
输出文件仅有一行为一个整数,即不同的分法数。
【样例输入】
7 3
【样例输出】
4
【样例解释】
四种分法为:1,1,5;1,2,4;1,3,3;2,2,3;

/*#include<bits/stdc++.h>
using namespace std;
int k,n,ans;
void dfs(int last,int sum,int p)//从last位置开始,划分的和sum;划分的段数p 
{
	int i;
	if(p==k)
	{
		if(sum==n)
			ans++;
		return;
	}
	for(i=last;sum+i*(k-p)<=n;i++)
		dfs(i,sum+i,p+1);
}
int main()
{
	cin>>n>>k;
	dfs(1,0,0);
	cout<<ans<<endl;
	return 0;
}
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int n,k,ans;
int f[250][10];//已经分j份,且该份的和是i;这里n是资源,待分配 
/*1.先初始化,任何当k=1的情况下仅有一种分法。
2.然后如分析3组建dp方程,从i中分j份的方案数 (f[i,j]) , 为 i-1 中分 j-1 份 ( f[i-1,j-1] ,即分出_1_)
 和 i-j 分 j 份 (f[i-j][j],因为分出1后剩下 i-j 个可分的1,j 次机会) 的方案数之和。
3.同时要注意一个小小的细节(未经实测,可能不会导致WA)。
*/ 
int main()
{
	cin>>n>>k;
	f[0][0]=1;//初始化 
	for(int t=1;t<=n;t++)//分出来的整块的大小 
		for(int i=t;i<=n;i++)//整个一块的大小 
			for(int j=1;j<=k;j++)//分的段数 
				f[i][j]+=f[i-t][j-1];
	cout<<f[n][k];//输出将整数n分成k份的分法 
	return 0;
}

例2:生日蛋糕(上下届剪枝,优化搜索顺序,可行性剪枝,最优性剪枝)

【问题描述】
一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。 从下往上数第i(1<=i<=M)层蛋糕是半径为Ri,高度为Hi的圆柱。当i<M时,Ri>Ri+1且Hi>Hi+1。希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。令Q=Sπ,请编程对给出的N和M,找出、适当的Ri和Hi的值,使S最小。(除Q外,以上所有数据皆为正整数) 【输入格式】
第一行为N(N≤10000),表示待制作的蛋糕的体积为Nπ;
第二行为M(M≤20),表示蛋糕的层数为M。
【输出格式】
输出仅一行,是一个整数S(若无解则S=0)。
附:圆柱公式:体积V=πR2H;侧面积A’=2πRH;底面积A=πR2

【算法分析】
在这里插入图片描述
搜索框架:从下往上搜索,枚举搜索面对的状态有:正在搜索蛋糕第dep层,当前外表面面积s,当前体积v,第dep + 1 层的高度和半径。不妨用数组h和r分别记录每层的高度和半径。整个蛋糕的“上表面”面积之和等于最底层的圆面积,可以在第M层直接累加到 s 中。这样在第 M-1层往上的搜索中,只需要计算侧面积。
在这里插入图片描述

剪枝:
上下界剪枝
在第dep层时,只在下面的范围内枚举半径和高度即可。
首先,枚举R∈[dep,min⁡(⌊√N−v⌋,r[dep+1]−1)]
其次,枚举H∈[dep,min⁡(⌊N−v/R2⌋,ℎ[dep+1]−1)]
上面两个区间右边界中的式子可以通过圆柱体积公式:πR2H=π(N-v)得到.
优化搜索顺序
在上面确定的范围中,使用倒序枚举。
可行性剪枝
可以预处理出从上往下前i(1≤i≤M)层的最小体积和侧面积。显然,当第1~i层的半径分别取1,2,3,…,i,高度也分别取1,2,3,…,i时,有最小体积与侧面积。
如果当前体积v加上1~dep-1层的最小体积大于N,可以剪枝。
最优性剪枝1
如果当前表面积s加上1~dep-1层的最小侧面积大于已经搜到的答案,剪枝。
在这里插入图片描述

最优性剪枝2
在这里插入图片描述
加入以上五个剪枝后,搜索算法就可以快速求出该问题的最优解了。

#include <cmath> 
#include <iostream>
#include <algorithm>
using namespace std;
const int INF = 0x7fffffff;
int n, m;/*n,m是给定的体积以及层数*/
int minv[30], mins[30], ans = INF;
int h[30], r[30], s = 0, v = 0;

void dfs(int dep) {
    if (!dep) {/*还在搜索的过程中*/
        if (v == n) ans = min(ans, s);/*体积已经全部用完了,此时的不合法的状态下的面积一定比合法的面积小*/
        return;
    }
/*sqrt(n - v):数学公式可以把半径进一步的缩小;
而r[dep + 1] - 1是因为当前dep层一定是比dep+1层小的,最少小一,这样子的话下面一层就会有更多的选择,h同r;
r[dep] >= dep是因为如果r小于dep的话,那么之后的层数一定不够m层,所以该结果不合法*/
    for (r[dep] = min((int)sqrt(n - v), r[dep + 1] - 1); r[dep] >= dep; r[dep]--)/*确定半径*/
        for (h[dep] = min((int)((double)(n-v) / r[dep] / r[dep]), h[dep+1] - 1); h[dep] >= dep; h[dep]--) 
        {
            if (v + minv[dep-1] > n) continue;
            if (s + mins[dep-1] > ans) continue;
            if (s + (double)2 * (n - v) / r[dep] > ans) continue;/*鬼畜剪枝*/
            /*用剩余体积估计之后的比实际要小的需要用到的表面积+s判断当前状态是否可行*/
            if (dep == m) s += r[dep] * r[dep];/*所有层的裸露的部分的面积*/
            s += 2 * r[dep] * h[dep];/*s=2rπh*/
            v += r[dep] * r[dep] * h[dep];/*v=r*r*πh*/
            dfs(dep - 1); 
            if (dep == m) s -= r[dep] * r[dep];/*回溯的现场还原*/
            s -= 2 * r[dep] * h[dep];
            v -= r[dep] * r[dep] * h[dep];
        }
}

int main() {
    cin >> n >> m;
    minv[0] = mins[0] = 0;/*初始化处理从上到下的每一层之前的最小的体积以及侧面积*/
    for (int i = 1; i <= m; i++) {
        minv[i] = minv[i-1] + i * i * i;/*有要求每一层的半径和高度都应该是逐个递增的*/
        mins[i] = mins[i-1] + i * i;
    }
    h[m+1] = r[m+1] = INF;/*因为一共只需要m层,在搜索的for循环中有体现*/
    dfs(m);/*搜索的顺序是从最大的一层到最小的一层*/
    cout << ans << endl;
    return 0;
}

例3:Addition Chains(优化搜索顺序)

【问题描述】
已知一个数列a0,a1…am(其中a0 = 1,am = n,a0 < a1 < a2 < … < am-1 < am)。对于每个k(1<=k<=m),需要满足ak=ai+aj(0 <= i, j <= k-1,这里i与j可以相等)。
现给定n的值,要求m的最小值(并不要求输出),及这个数列的值(可能存在多个数列,只输出任一个满足条件的就可以了)。
【输入格式】
多组数据,每行给定一个正整数n。输入以0结束。
【输出格式】
对于每组数据,输出满足条件的长度最小的数列。

【算法分析】
由于ak=ai+aj(0≤i,j<k),所以在搜索的过程中可采用由小到大搜索数列的每一项的搜索顺序进行试算。一般搜索时习惯于从小到大依次搜索每个数的取值,但是在这到题目中按照这样的顺序搜索编程运算其结果(效率)十分不理想。
由于题目要求的是m的最小值,也就是需要我们尽快得到数n,所以每次构造的数应当是尽可能大的数,根据题目的这个特性,我们将搜索顺序改为从大到小搜索每个数。后一种搜索顺序得到的程序效率大大地优于第一种搜索顺序得到的程序。
可见,选择合适的搜索顺序对于提高程序的效率是非常重要的,运用良好的搜索顺序来对搜索题目进行优化是性价比很高的技巧。

#include<cstdio>
#include<cstring>
using namespace std;
int a[105],bo[105],ans[105];
int n,m;
bool check(int v,int x)
{
	for (int i=0; i<=x; ++i)
	if (v-a[i]>=0 && bo[v-a[i]]) return 1;
	return 0;
}
void dfs(int x)
{
	if (x>=m) return;
	if (a[x]==n) {
		if (x<m) m=x,memcpy(ans,a,sizeof(ans)); 		
		return;
	}
	for (int j=n; j>a[x]; --j)
	if (check(j,x)){
		a[x+1]=j;
		bo[j]=1;
		dfs(x+1);
		bo[j]=0;
	}
	return;
}
int main()
{
	bo[1]=1;
	a[0]=1;
	while (scanf("%d",&n),n){
		for (int i=2; i<=n; ++i) bo[i]=0;
		m=1e5;
		dfs(0);
		for (int i=0; i<=m; ++i) printf("%d ",ans[i]);
		printf("\n");
	}
}

标程2

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define INF 0X3F3F3F3F
#define N 2019
using namespace std;

int n,minn,ans[N],a[N];
//n是序列的最大值
//minn是拆数字最小层数 
//ans存答案
//a是一个过程中起记录作用的数组 

void dfs(int x)  //递归第x层 
{
    if(x-1 > minn) return ;  //超过最小层数,剪枝 
    if(a[x-1] > n) return;   //上一层的数字大于最大值n,剪枝 
    if(a[x-1] == n)  //上一层的数字=n,有希望构成一个完整解答 
    {
        if((x-1) >= minn) return; //层数超了 
           minn = x-1;   //记下暂定答案 
        for(int i=1;i < x;i++) ans[i] = a[i];
    }
    else
    {
        for(int j = x-1;j >= 1;j--)  
        //ak=ai+aj,倒着枚举,看看能否继续扩展下一层 
        {
            if(a[x-1]+a[j] <= n)
            {
                a[x] = a[x-1]+a[j];  //可以继续扩展 
                dfs(x+1); 
                a[x] = 0;  //回溯 
            }
        }
    }
}

int main()
{
    while(scanf("%d",&n) != EOF) //输入非空 
    {
        if(n == 0) break;
        else
        {
            a[1] = 1;  //初始化第一层为1 
            minn = INF;  //拆数字的最小层数 
            dfs(2);  //从第二层开始递归 
            for(int i = 1;i <= minn;i++) printf("%d ",ans[i]); //确定层数,输出答案 
            printf("\n");
        }
    }
    return 0;
}

例4:小木棍(多种剪枝策略)

【问题描述】
同样长的小木棍被随意砍成几段,已知每段的长都不超过50。现在,要把小木棍拼接成原来的样子,但却不知道开始时有多少根木棍和它们的长度。给出每段小木棍的长度,求原始木棍的最小可能长度。
【文件输入】
第一行为一个单独的整数 N 表示砍过以后的小木棍的总数,其中N<=60;
第二行为 N 个用空个隔开的正整数,表示 N根小木棍的长度。
【文件输出】
输出文件仅一行,表示要求的原始木棍的最小可能长度。

【算法分析】
要得到最小的原始木棍长度,可以按照分段数的长度,依次枚举所有的可能长度len;每次枚举len时,深搜判断是否能用截断后的木棍拼合出整数个len,能用的话,找出最小的len即可。对于1S的时间限制,用不加任何剪枝的深搜时,时间效率为指数级,效率非常低。对于此题,可从可行性和最优性上加以剪枝。
从最优性上来分析,可以剪枝以下2点:
①设所有木棍的长度和是sum,那么原长度(也就是需要输出的长度)一定能够被sum整除,即一定是拼出整数根。
②木棍原来的长度一定大于等于所有木棍中最长的那根。
综合上述两点,可知原木棍的长度len在最长木棍的长度到sum之间,且sum能被len整除。所以,在搜索原木棍的长度时,可以设定为从截断后所有木棍中最长的长度开始,每次增加长度后,必须能整除sum。
从可行性上来分析,可以再剪枝以下7点:
③短木棍比长木棍更灵活组合,所以可以对木棍按长度从大到小排序。
④在截断后的排好序的木棍中,当用木棍i拼合原始木棍时,可以从第i+1后的木棍开始搜。
⑤用当前最长长度的木棍开始搜,如果拼不出当前设定的原木棍长度len,则直接返回,换一个原始木棍长度len。
⑥相同长度的木棍不要搜索多次。用当前长度的木棍搜下去得不出结果时,可以提前返回。
⑦判断搜到的几根木棍组成的长度是否大于原始长度len,如果大于,可以提前返回。
⑧判断当前剩下的木棍根数是否能够拼的木棍根数,如果不够,肯定拼合不成功,直接返回。
⑨找到结果后,在能返回的地方马上返回到上一层递归处。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 66;
int n,sum,nm=maxn,mx,d;
int len[maxn],a[maxn],pre[maxn];
void dfs(int u,int k,int p)
{
	//当前长棍还有u没有拼,还有k根长棍没有拼,当前长棍的最短短棍长度是p
	if(u==0)
	{
		dfs(d,k-1,a[n]);
		return;
	 } 
	 if(k==0)
	 {
	 	cout<<d;
	 	exit(0);
	 }
	 p=(p<u)?p:u;
	 while(p&&len[p]==0)--p;
	 while(p)
	 {
	 	if(len[p])//枚举当前棍长 
	 	{
	 		--len[p];
	 		dfs(u-p,k,p);
	 		++len[p];
	 		if((u==p)||(u==d))return;
	 		p=pre[p];
		 }
		 else p=pre[p];
	 }
}
int main()
{
	cin>>n;
	for(int i = 1,x;i<=n;++i)
	{
		cin>>x;
		sum+=x;
		++len[a[i]=x];
	}
	sort(a+1,a+1+n);
	for(int i = 1;i<=n;i++)//预处理长度i的上一个长度
	{
		if(a[i]!=a[i-1])pre[a[i]]=a[i-1];
	 } 
	 for(d=a[n];(d<<1)<=sum;++d)
	 {
	 	if((sum%d)==0)
	 		dfs(d,sum/d,a[n]);
	 }
	 cout<<sum;
	 return 0;
}

练习题目:(www.51nod.com,自行注册,免费)
1、分成2组
2、邻之差为k
3、挑选数字
4、小明爱正方形

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

搜索优化剪枝策略 的相关文章

随机推荐

  • [矩阵的三角分解系列六] Eigen中的三角分解

    Eigen中的三角分解 简介 安装命令 三角分解函数 使用范例 矩阵的三角分解是求解线性方程组常用的方法 包括LU分解 LDU分解 杜利特 Doolittle 分解 克劳特 Crout 分解 LLT 乔累斯基Cholesky 分解 LDLT
  • 阿里云 Serverless 应用引擎 2.0,正式公测!

    阿里云 Serverless 应用引擎 SAE2 0 正式公测上线 全面升级后的 SAE2 0 具备极简体验 标准开放 极致弹性三大优势 应用冷启动全面提效 秒级完成创建发布应用 应用成本下降 40 以上 此外 阿里云还带来容器服务 Ser
  • 虚拟数字人定制公司 国内做虚拟数字人定制开发的公司有吗?

    得益于图形渲染技术 AI技术 传感器硬件等技术的发展 使得虚拟数字人逐步进入大众视野 虚拟数字人分为真人驱动 AI驱动 AI合成 不同形式的虚拟数字人制作难度与成本相差较大 许多大众认为 制作虚拟数字人就是做一个美术就可以了 如果这样的话
  • 相机标定系列---opencv相关标定算子

    目录 1 标定的相关介绍 2 算法流程及相关算子简介 1 算法流程主要有五部分 2 相关算子介绍 1 棋盘标定板查找角点 2 亚像素角点准确化 3 可视化角点 4 相机标定 5 误差计算 3 完整代码 1 标定的相关介绍 1 标定的目的 在
  • MSBuild version 与 ToolsVersion 的区别

    MSBuild version 是指MSBuild所在的Framework的版本 ToolsVersion 是指编译当前工程使用的版本 相当于MSBuild的 ToolsVersion 参数 如果一个MSBuild 脚本中 既含有Tools
  • 理解make update-api命令

    一 使用场景 增加系统API 修改 hide的API 修改公共API 存在以上修改后 都需要先执行make update api 然后再make 二 缘起 1 在以上使用场景下 编译系统源码都会出现如下提示 see build core a
  • Python——信号量、条件变量、事件

    1 信号量 Semaphore 信号量通常用于保护数量有限的资源 例如数据库服务器 在资源数量固定的任何情况下 都应该使用有界信号量 在生成任何工作线程前 应该在主线程中初始化信号量 信号量提供acquire方法和release方法 每当调
  • Summer Holiday HDU - 1827 Tarjian

    题目链接 HDU 1827 主要思路 先用Tarjian处理出强联通块 然后将每个点的边转为强联通块之间的边 然后连上一个个入度为0的强联通块中最小的结点即可 正确性解释 用Tarjian算法处理出强连通块之后把每个强联通块看成是一个点 故
  • 蓝牙模块AT模式AT指令

    文章目录 进入AT模式的两种方法 HC 05的AT指令 HC 06的AT指令 进入AT模式的两种方法 经过摸索 这里总结两种进入AT模式的方法 与USB转TTL相连后接入电脑 将波特率设置成9600 模块指示灯快闪 这时再按下模块的按钮便进
  • 解决idea中不能输出中文问题

    在CSDN上查明了问题 解决方法 gt gt gt gt gt 打开idea 点在左上角的File 找到Settings 点击找到File Encodings然后看上面的Global Encoding和Project Encoding 都选
  • 埋点理论以及基于Vue的前端埋点技术

    一 明确目标 要知道利用埋点获取数据是要做什么样的用户分析 二 获取数据 三 埋点技术 四 已有软件 五 声明式埋点的实现 利用Vue的自定义指令原理 比如下边监控按钮点击的埋点 在main js中定义全局自定义指令 Vue directi
  • 【springboot】解决springboot项目,扫描不到自定义的controller

    如果遇到 http localhost 8080 test访问出现这个异常This application has no explicit mapping for error so you are seeing this as a fall
  • ASP.Net+SQLserver部署到阿里云Windows版本服务器

    工具 1 阿里云云服务器ECS 2 Windows 7 专业版 3 Visual Studio 2017 4 SQL Server2008 R2 写在前面 网站建立好之后 部署在本地服务器 只能局域网内访问 如果想让外网可以访问该网站 我们
  • spring boot配置shiro安全框架及用户登录权限验证实现

    关于shiro的配置我单独拿出来写了 从数据库表建立 到配置 如何使用 连接地址为shiro安全框架 shiro的应用理解 如果有修改会在这里边修改的 另外 springboot shiro的项目已分享到GitHub上 如果需要的可以看下
  • Scratch3.0连接EV3,WEDO2.0的方法视频讲解。

    为什么讲这个问题 最近因为国内对Scratch服务器访问的限制 现在访问官网和链接Scratch服务器的功能模块都使用不成了 离线版本中EV3和WEDO链接上也出了问题 目前这个是可以解决的 这个问题怎么解决 其实主要还是修改电脑的host
  • linux服务器搭建心得

    安装显卡 非必要不去官网手动安装 容易出错 ubuntu 查看显卡 ubuntu drivers devices 根据输出安装 sudo apt install nvidia deiver xxx 重启 reboot 检查安装 nvidia
  • 队列实现栈(你看我讲的是不是最细的就完了)

    最伟大的成就往往起源于最强烈的热情 诺曼 文森特 皮尔目录 一 队列实现栈 二 使用两个队列来模拟实现栈 1 栈结构体包含两个队列 2 创建一个结构体的指针 3 myStackPush入栈操作 4 myStackPop出队列操作并返回剩下的
  • 【浙工商期末报告】研一Python期末作业B题(思路分享+源代码分享+原报告分享)

    目录 研一Python期末作业B题 思路分享 一 题目介绍 1 1 A题 1 2 B题 二 B题思路讲解 2 1 问题的引入 2 2 不平衡数据集 2 2 1 不平衡数据的实例 2 2 2 不平衡数据集导致的问题 2 2 3 不平衡数据集的
  • 完备的AI学习路线(三)基础知识之python编程

    入门人工智能领域 首推Python这门编程语言 1 Python安装 Python安装包 我推荐下载Anaconda Anaconda是一个用于科学计算的Python发行版 支持 Linux Mac Windows系统 提供了包管理与环境管
  • 搜索优化剪枝策略

    状态剪枝 如果将搜索的状态看作结点 状态之间的转移看作边 搜索的过程就构建出了一棵 搜搜树 其实 这也是判定搜索题目的一个方法 所谓 剪枝 就是在搜索树上去掉一些枝杈不进行搜索 从而减少小时间消耗 搜索树的剪枝如下图所示 常用的思考策略 1