埃及分数—题解 && 深搜的简单剪枝
一、搜索的剪枝必需遵循三个原则:
1、正确性(不能把正解排除,要不然搜什么呢?)2、准确性(尽可能把不能通向正解的枝条剪去)3、高效性(因为在每个枝条上都要进行一次判断,如果判断的复杂度很高,也会相应地给总复杂度“加料”)
二、剪枝的常用入手点(上面说的都是套话,下面可是干货了)
1、搜索顺序(例如有些题要求不能重复且解中最大值在所有解的最大值中最小的最优解,可以按照从小到大设计搜索,否则其他顺序不仅搜索效率低,代码相应也会很繁琐);
解释一下为什么书中说不一样的搜索顺序,相应的搜索树的形态结构也不一样:当不加任何剪枝时,最直观的体现就是树从根节点开始分支不一样多。因为最终的可行解是一样多的(先不考虑最优性排除的非最优解),而只有搜索树搜到了叶节点才会搜到可行解。如果搜索深度也一样,那么两个搜索顺序对应的搜索树即为两个深度、叶节点树相同,但节点分支的递增顺序不同(可能是一个数较浅地方的分支较小,另一个反之...)。进行剪枝后,情况就不一样了:剪枝时,若剪去的枝条规模越大,那么剪枝也就越成功。那么,在一定的深度剪枝时,对更深的地方分支多的那颗搜索树剪枝的效果就要比对另一颗更深的地方分支少的搜索树剪枝的效果好。因此在剪枝判断很难继续优化的前提下,搜索顺序也会很大程度地影响剪枝的效果。
2、搜索对象:不一样的搜索对象,一般相应的复杂度也不一样。
3、可行性剪枝:当搜索到一个节点时,发现已经不能通向解,就立即回溯。更强一步:对以后的节点,只走能通向解的节点。
*上下界剪枝:有些题仔细分析会发现一个节点的分支是有一个区间,要靠我们分析出区间的上下界,叫上下界剪枝(可行性剪枝中非常常用的一类剪枝,以至于都有自己的名字了(滑稽))。
4、最优性剪枝:当搜索到一个节点时,发现当前状态已经比已得到的最优解差,这是无论向下走的策略再怎么好,也不能比已知解更优,所以立即回溯。更强一步:发现当前状态在未来不会优于已知最优解回溯。
5、排除等效冗杂/记忆化:当发现当前状态已经搜过(记忆化),或当前状态的等效状态已经搜过,就不往下搜了。
总结:搜索时面临的状态其实都有多个“维度”,或是说多个方面(长度,体积,位置,顺序,密度,增长率至整体的每个基础的组成元素等等),这些维度常常出现在题目描述、给出的变量、要求求的解中。仔细阅读,找到这些维度,结合题目的约束条件或这些维度自己本身的性质,思考剪枝的入手点,根据可行性、最优性或界限列出有关当前花费的不等式,即为初步剪枝。初步剪枝不足以满足AK的可以再考虑列出有关未来花费的不等式(有时用到预处理),还可以再结合各维度之间的关系(例如圆半径与面积、位置与重力势能的关系等等,能懂就好)列出判断用不等式。这不就是推导不等式么。
相关例题:埃及分数
由于决定最优解的第一条件是加数的个数,所以我们可以先枚举加数的个数limit,尝试limit个单位分数将a/b分解(迭代加深搜索)。为了避免重复搜索,假设第一个单位分数>第二个单位份数>第三个单位分数>....;即第一个分母<第二个分母<第三个分母...这样我们确定的搜索顺序——个数从小到大,对于每个个数,分母从小到大。
设当前的分母为xk,a/b还剩rest没有分解完,因为1/xk+1/xk+1+..+1/xlimit=rest,1/xk>1/xk+1>...>1/xlimit,所以1/xk>rest/(limit-k+1),即xk<(limit-k+1)/limit。因为1/xk<rest,所以xk>1/rest。因为分母按从小到大的顺序搜索,所以xk有大于xk-1。这样就进行了一波强大的上下界剪枝。
当xk>=已知解的最大的分母maxans时,再进行下去也没有意义了(因为无论如何解都不会被这条路径更新了),所以xk<maxans。又进行了一波最优性剪枝。
tips:对于分数的存储,最好不要算出值来存,因为浮点数总是有误差的,虽然一开始的误差很小,但在在大量的运算下,很容易就把误差放得很大,很大,很大(重要的事情说三遍),所以最好用分子分母存储法。但也注意分子分母可能也很大,很大,很大(重要的事情说三遍),因此别忘了数据类型开大点,能约分就约分(约分有时还能直接撞到最优解)。
AC代码:
#include<bits/stdc++.h>
#define re register int
#define ll long long
using namespace std;
ll a,b,m=1,ans[2000],now[2000];
bool bj;
ll gcd(ll a,ll b)
{
if(!b) return a;
return gcd(b,a%b);
}
void yf(ll &a,ll &b)
{
int g=gcd(a,b);
a/=g;
b/=g;
return;
}
void dfs(ll x,ll p,ll A,ll B)
{
ll a=A,b=B;
yf(a,b);
if(x==1)
{
if(a==1 && b>p)
{
if(bj && b>=ans[1])
{
return;
}
now[1]=b;
bj=1;
memcpy(ans,now,(m+1)<<3);
return;
}
return;
}
for(re i=p;ceil((double)b*x/a)>=i;i++)
{
now[x]=i;
dfs(x-1,i,a*i-b,b*i);
}
return;
}
int main()
{
//freopen("fraction.in","r",stdin);
//freopen("fraction.out","w",stdout);
cin>>a>>b;
yf(a,b);
while(!bj)
{
dfs(m,1,a,b);
m++;
}
for(re i=m-1;i>=1;i--)
{
cout<<ans[i]<<" ";
}
return 0;
}
Orz