参见poj1011,HDU1455,洛谷1120,codevs3498小木棍
题外话
作死的木棍……
写了一下午……我好弱……
以前在codevs上A了这题,以为做出来了(才怪),后来才发现只有codevs上能A……
反正这是一道剪枝神题,如果剪错啦,就WA啦。我一开始是因为做了一个神奇的判断:如果在木棍为0的情况下拼不出来,不进行任何新的枚举。后来发现这错的很离谱啊……
另外,CSYZOJ上好像无论你写什么神奇的代码都能A这题?
神一样的剪枝
小木棍:题目给出长度的木棍。木棍:拼凑得到的木棍。
剪枝1:只有是原来所有小木棍长总和的约数作为答案才有可能。
剪枝2:答案从原始小木棍中最长的一根的长度开始枚举。
剪枝3:如果枚举到了最后一根木棍,直接返回。(因为有了剪枝1,只要枚举到最后一根,最后一根肯定能成)
剪枝4:如果用这个长度的小木棍去搜不行,那么所有这种长度的小木棍都跳过。
剪枝5:如果在木棍长度当前为0(也就是去选拼凑这根木棍的第一根小木棍)时失败,直接返回(因为无论如何这一根小木棍都是要被选的)
剪枝6:设定循环枚举小木棍的下界,每次都只能往后枚举。
加上所有这些神一样的剪枝就能过了!
语言渣,不懂看代码。
代码
#include<iostream>
#include<cstdio>
#include<climits>
#include<algorithm>
#include<cstring>
using namespace std;
int a[70];bool vis[70];
int n,tot,ans,cnt;
bool dfs(int x,int s,int p){//x:找了几根木棍,s长度,p减少循环次数
if(s==ans){s=0;x++;p=0;}
if(x==cnt)return 1;//当x=cnt时,后面一根肯定能拼出
for(int i=p+1;i<=n;i++){
if(a[i]==a[i-1]&&!vis[i-1])continue;//如果和前一根一样不用重复枚举
if(vis[i]||s+a[i]>ans)continue;
vis[i]=1;if(dfs(x,s+a[i],i))return 1;
vis[i]=0;if(!s)return 0;//如果一根新的木棍连第一个都不行,就不行(因为第一个总要上的)
}
return 0;
}
bool cmp(int x,int y){return x>y;}
int main()
{
int i,j;
while(1){
scanf("%d",&n);
if(!n)break;tot=0;
for(i=1;i<=n;i++){scanf("%d",&a[i]);tot+=a[i];}
sort(a+1,a+1+n,cmp);
for(ans=a[1];ans<=tot;ans++)
if(tot%ans==0){
cnt=tot/ans;memset(vis,0,sizeof(vis));
if(dfs(0,0,0))break;
}
printf("%d\n",ans);
}
return 0;
}
常用剪枝总结
1.最优性剪枝
假如即使后面取完美最优决策,都不可能更新ans的值,剪枝。(很多题都是这样)
假如当前ans已经更新为了可能的最优决策,剪枝。(HDU1044)
2.可行性剪枝
如果做了某个决策后就不可能达到目标状态,剪枝。(codevs1064虫食算)
3.可能最优剪枝
如果倒着搜比正着搜更容易搜到目标就倒着搜之类的,通过改变搜索顺序达到剪枝的目的。(codevs1064虫食算是这样的,但是有时候枚举顺序这个剪枝是无比玄学的)
持续更新……
4.优先扩展可能性少的状态
优先搜索某些可能性较少位置的状态,减少搜索树每一层的宽度(洛谷P1074 靶形数独)