典型的深搜 + 剪枝策略
我们采用可行性剪枝, 上下界剪枝, 优化搜索顺序剪枝, 最优性剪枝的方面来帮助我们进行剪枝.
也许有人还不知道剪枝, 那我就弱弱地为大家补习一下吧 qwq:
1. 优化搜索顺序:
在一些搜索问题中, 搜索树的各个层次, 各个分支之间的顺序是不固定的. 不同的搜索顺序会产生不同的搜索树形态, 其规模大小也相差甚远. 因此, 我们可以采用排序, 更改等手段来优化时间或者空间上的复杂度, 借此来优化我们的程序.
2. 排除等效冗余
在搜索过程中, 如果我们能够判定从搜索树的当前节点沿着某几条不同的分支到达的子树是等效的, 那么只需要对于其中的一条分支进行搜索. 这中优化的方法应用的时候十分有效(而且只需要一个 "=="qwq).
3. 可行性剪枝
在搜索过程中, 及时对当前状态进行检查, 如果发现分支已经无法到达递归的边界, 就执行回溯. 这就好比我们在道路上行走时, 远远地看到前方是一个死胡同, 就应该立即折返绕路, 而不是走到这个死胡同的尽头再返回.
某些题目的范围限制是一个区间, 此时可行性剪枝也被称为 "上下界剪枝".
4. 最优性剪枝
在最优化问题的搜索过程中, 如果当前花费的代价已经超过当前搜索到的最优解, 那么无论采取多么优秀的策略到达递归边界, 都不可能更新答案. 此时可以停止对当前分支的搜索, 执行回溯.
5. 记忆化
可以记录每个状态的搜索结果, 在重复遍历一个状态时直接检索并返回. 这好比我们对图进行深度优先遍历时, 标记一个节点是否已经被访问过.(类似于 visit[], 或者像线段树懒标记之类的. 我是这么理解的 qwq)
题目背景
7 月 17 日是 Mr.W 的生日, ACM-THU 为此要制作一个体积为 Nπ的 M 层
生日蛋糕, 每层都是一个圆柱体.
设从下往上数第 i(1<=i<=M)层蛋糕是半径为 Ri, 高度为 Hi 的圆柱. 当 i<M 时, 要求 R_i>R_{i+1}Ri>Ri+1且 H_i>H_{i+1}Hi>Hi+1.
由于要在蛋糕上抹奶油, 为尽可能节约经费, 我们希望蛋糕外表面 (最下一层的下底面除外) 的面积 Q 最小.
令 Q= Sπ
请编程对给出的 N 和 M, 找出蛋糕的制作方案(适当的 Ri 和 Hi 的值), 使 S 最小.
(除 Q 外, 以上所有数据皆为正整数)
题目描述
输入输出格式
输入格式:
有两行, 第一行为 N(N<=20000), 表示待制作的蛋糕的体积为 Nπ; 第二行为 M(M<=15), 表示蛋糕的层数为 M.
输出格式:
仅一行, 是一个正整数 S(若无解则 S=0).
样例
输入样例:
100 2
输出样例:
68
下面我们进行一下这个问题的求解思路:
1. 上下界剪枝:
当我们在第 dep 层的时候, 我们只在下面的范围枚举半径和高度即可.
首先, 枚举 R∈[dep,min($\sqrt{N-v}$,r[dep+1]-1)];
其次, 枚举 H∈[dep,min($\sqrt{N-v}/R^2$,h[dep+1]-1)];
2. 优化搜索顺序
在上面确定的范围中进行倒序枚举.
3. 可行性剪枝(画个五角星)
可以预处理出从上往下前 i(1≤i≤M)层的最小体积和侧面积.
显然, 当第 1~i 层的半径分别取 1,2,3...i 时, 有最小体积和侧面积.
如果当前体积 V 加上 1~dep-1 层的最小体积大于 N, 则可以剪枝.
4. 最优性剪枝(1)
如果当前表面积 s 加上 1~dep-1 层的最小侧面积大于已经搜到的结果, 剪枝.
5. 最优性剪枝(2)
利用 h 和 r 数组, 1~dep-1 层的体积可表示为 n-v=∑(dep-1,k=1)h[k]*r[k]^2,1~dep 层的表面积可以表示为 2∑(dep-1,k=1)h[k]*r[k].
因为 2∑(dep-1,k=1)h[k]*r[k]=2/r[dep]*∑(dep-1,k=1)h[k]*r[k]*r[dep]≥2/r[dep]*∑(dep-1,k=1)h[k]*r[k]^2≥2(n-v)/r[dep].
所以当 2(n-v)/r[dep]+s 大于已经搜到的结果时, 可以剪枝.
加上以上五个剪枝后, 搜索算法就可以快速求出该问题的最优解.
完整代码如下:
- #include<iostream>
- #include<cstdio>
- #include<cmath>
- #include<algorithm>
- using namespace std;
- int r[10086],h[10086],n,m,dep,minn=0x7fffffff;
- void dfs(int dep,int leftdep,int leftv,int nowv)
- {
- if(leftv<0)
- return ;
- if(dep>m+1)
- return ;
- if(nowv>=minn)
- return ;
- if(leftv==0&&dep==m+1)
- {
- nowv+=r[1]*r[1];
- minn=min(nowv,minn);
- return ;
- }
- if(nowv+leftdep+r[1]*r[1]>minn)
- return ;
- if(leftv-(r[dep-1]*r[dep-1]*h[dep-1]*leftdep)>0)
- return ;
- for(int i=r[dep-1]-1;i>=leftdep;i--)
- for(int j=h[dep-1]-1;j>=leftdep;j--)
- {
- if(leftv-i*i*j>=0&&dep<=m)
- {
- r[dep]=i;
- h[dep]=j;
- dfs(dep+1,leftdep-1,leftv-i*i*j,nowv+(2*i*j));
- r[dep]=0;
- h[dep]=0;
- }
- }
- }
- int main()
- {
- scanf("%d%d",&n,&m);
- r[0]=sqrt(n);
- h[0]=sqrt(n);
- dfs(1,m,n,0);
- if(minn==0x7fffffff)
- {
- printf("0");
- return 0;
- }
- printf("%d",minn);
- return 0;
- }
写完了长长的博文, 下面来总结一下心得吧 qwq:
1. 搜索算法面对的状态我们可以将其理解为一个岔路口的选择问题, 也就是可以看作一个多元组, 其中每一元都是问题状态空间中的一个 "维度".
这其中每一个维度发生变化, 都会移动状态空间中的一个 "点".
而这些维度通常在题目中也会出现, 这些便需要大家在做题的时候多留心观察一下我们所需要的条件以及我们根据这些维度的变化能够得到什么, 从而得出合理的搜索框架.
2. 搜索过程中的剪枝, 其实我们就是针对于每一个 "维度" 与其该维度的边界条件, 加以缩放, 推导, 得出一个相应的不等式, 以减少搜索树的扩张从而达到优化复杂度的目的.
3. 为了进一步提高我们剪枝的 "效率" 以及我们的所要求的目的, 我们可以除了对当前所花费的代价之外, 也对该状态未来的 "状态" 进行分析, 有时我们还可以跨维度进行优化分析, 达到我们的要求(也就是全面优化 qwq), 同时这也更容易接近我们的目的.
手写不易, 各位客官点个赞呗(花花)
来源: https://www.cnblogs.com/lyp-Bird/p/10387860.html