剪枝
剪枝,就是减小树规模,尽早排除搜索树中不必要的分支,也就是对搜索的优化。
以下是几种常见剪枝方法:
- 优化搜索顺序(比如下一题的木棒,优先尝试长的,长的不行,直接返回)
- 排除等效冗余(木棒中四种剪枝就是如此,几种分支等效,对一个进行搜索,失败直接返回)
- 可行性剪枝(可以看见的死胡同,可以直接返回)
- 最优化剪枝(记录最优解,此时花费超过最优解,可以直接返回,木棒:len是递增,先得出就是最优,就不用考虑这个了)
- 记忆化(记录每个状态的搜索结果,重复时,直接检索并返回)
AcWing 167. 木棒
思路
在上面的剪枝方法中已经简单提到了,这个题的剪枝。
下面是这个题的主要四个剪枝(代码中也有注释):
- 拼接时,按递减顺序加入,防重混乱
- 该长度拼接失败,后面再遇到,跳过
- 第一根就失败,直接返回
- 如果第一根恰好拼接成功,后面出现问题,len不符合,返回
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,a[65],len,cnt,v[100];//v用来标记是否选择
bool dfs(int stick,int current,int pos)
{//拼装第stick根,当前长度为current,从pos处选择木棒
if(stick>cnt) return true;//已经拼装好cnt根,搜索成功
if(current==len) return dfs(stick+1,0,1);//长度=len,第stick已经拼装好
int fail=0;//剪枝2:去重,相同长度不再判断
for(int i=pos;i<=n;i++)//剪枝1:递减枚举,以防重复混乱
{
if(!v[i]&¤t+a[i]<=len&&fail!=a[i])
{
v[i]=1;//标记此木棒已选
if(dfs(stick,current+a[i],i+1)) return true;
//递归接着拼,直至stick>cnt,返回 true,if为真,返回true;
fail=a[i]; //没能return true。a[i]不可拼接,记录一下
v[i]=0;//取消标记
if(current==0||current+a[i]==len)
return false;
//剪枝3:current=0,连符合的第一段都找不到,直接返回
//剪枝4:current+a[i]==len,第一段拼好,后面出问题直接返回,因为拼接是从长到短选的,
//有点贪心的感觉
}
}return false;
//所有分支都没合适的,失败
}
signed main()
{
IOS
while(cin>>n&&n)
{
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum+=a[i];
}
sort(a+1,a+n+1,greater<int>() );//先从最大的组合,确定顺序
for(len=a[1];len<=sum;len++)//从小到大判断,求的是最小len
{
if(sum%len) continue;
cnt=sum/len; //确定了判断对象:长度为len,一共cnt根
memset(v,0,sizeof(v));//初始化为0,每一根都没选择
if(dfs(1,0,1)) break;//要拼接的第一根,当前长度为0,从a[1]开始选择
}
cout<<len<<'\n';
}
}