一道不错的剪枝题,用到的剪枝优化比较多,剪枝思路也值得学习
在这里我只采取了能通过此题的剪枝,似乎还可以继续优化?
剪枝讲解在注释里
#include<iostream>
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=70;
inline int read(){
int ret=0;
int f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-f;
ch=getchar();
}
while(ch<='9'&&ch>='0'){
ret=ret*10+(ch^'0');
ch=getchar();
}
return f*ret;
}
int n;
int a[maxn];
int fla[maxn];
int vis[maxn];
int last;
int x;
bool cmp(int x,int y){
return x>y;
}
inline bool dfs(int nu,int len,int ls,int t){
if(nu>x){
return true;//达到了我们所需的原木棍数,所以该划分方法正确
}
if(len==t){
return dfs(nu+1,0,1,t);//当前状态符合题意,进入下一状态
}
last=0;
/*
考虑到我们为什么要从ls开始枚举?
la是该木棒上一个枚举的木棍的下标,因为木棍数组是降序排序的
具有单调性,ls是个较大的值,在ls前的数一定是大于la的数
我们已知,上一个状态的len+a[ls]=目前的len,所以,a[ls]是<=t-len(上一状态)&&符合题意的最大值
所以当前状态直接由ls开始枚举即可
*/
for(int i=ls;i<=n;i++){
if(!vis[i]&&len+a[i]<=t&&last!=a[i]){
/*
这里同样存在剪枝last为上个不符合状态的值
在上个状态选择它不能得到正确答案,当前状态的值假设和它相同,同样也找不到正确值
*/
vis[i]=1;
if(dfs(nu,len+a[i],i,t))
return true;
vis[i]=0;
last=a[i];
if(t-len==a[i]) return false;
/*此步剪枝比较重要
此步的意思就是当该木棒需要拼接的长度和当前木棍长度相等时却拼接失败
直接返回失败
我们为什么要这样剪枝?
当剩余长度等于当前枚举长度时,把该木棍接到木棒上是最优的
为什么这样最优?
假设剩余的木棍中也可以拼成剩余长度,那用到的木棍一定多个长度小于剩余长度的木棍
用多个长度较小木棍来拼接对之后的拼接会产生较大的影响,但直接用一根是没有影响的,所以
如果无法直接用一根木棍拼接,
剩余木棍一定也不行
所以直接返回错误
*/
if(len==0){
return false;
/*
len=0说明在枚举一个新的木棍,我们已知,假设剩余的木棍可以组成木棒,那么当前位置无论放剩下的哪一个也一定可以
组成一组新的木棒,所以当无法构成新木棒时直接return ;
*/
}
}
}
return false;
}
/*
剪枝和dfs都非常巧妙
将目标状态分为两个部分
1 凑齐当前木棍
2 还原所有木棍
第二个状态由1 组成
*/
int main(){
freopen("a.in","r",stdin);
n=read();
int tot;
int l=0;
int r=0;
for(int i=1;i<=n;i++){
x=read();
if(x>50)
continue;
tot++;
a[tot]=x;
l=max(x,l);
r+=x;
}
n=tot;
sort(a+1,a+1+n,cmp);
memset(vis,0,sizeof(vis));
for(int i=l;i<=r;i++){
if(r%i){
continue;
}
x=r/i;
if(dfs(1,0,1,i)){
cout<<i;
break;
}
}
return 0;
}