传送门:洛谷P1120
题解
先贴一篇很好的博客:题解 P1120 【小木棍 [数据加强版]】-Zap
一些剪枝:
dfs(int k,int last,int rest)
k表示正在拼第几根原来的长棍,last表示使用的上一根木棍,rest表示当前
在拼的长棍还有多少长度未拼。
关于长度:
1.枚举总长度sum [maxlen,sum]之间的sum的约数
2.枚举到sum/2即可,再大只能是sum了
关于木棍:
1.优先填长的木棍
2.当用木棍i拼合长棍时(last=i),从第i+1根木棍开始往后搜
3.dfs返回拼接失败时,更换当前使用的木棍时直接跳到下一个长度严格
小于该木棍的木棍。(预处理next)
4.二分找第一个长度<=rest的木棍
关于直接回溯:
1.当前长棍剩余的未拼长度等于当前木棍的长度(剩下的较短的木棍都无法组成几根长棍,换成长棍更不可能)
2.当前长棍剩余的未拼长度等于原始长度(怎么都不可能了)
关于vs数组:
回溯时清0就不用memset了。
搜索(剪枝)是基本功,要练好啊
代码
#include<bits/stdc++.h>
using namespace std;
int n,m,a[70],sum,len,cot,vs[70],nxt[70];
inline int fd(int r,int x)
{
int l=1,re=0,mid;
for(;l<=r;){
mid=(l+r)>>1;
if(a[mid]<=x) l=(re=mid)+1;
else r=mid-1;
}
return re;
}
bool dfs(int k,int last,int rest)
{
if(!rest){
if(k==cot) return true;
int i;
for(i=n;i;--i) if(!vs[i]) break;
vs[i]=1;
if(dfs(k+1,i,len-a[i])){vs[i]=0;return true;}
else{vs[i]=0;return false;}
}
int i=fd(last-1,rest);
for(;i;--i) if(!vs[i]){
vs[i]=1;
if(dfs(k,i,rest-a[i])){vs[i]=0;return true;
}else if(a[i]==rest || rest==len){vs[i]=0;return false;}
vs[i]=0;i=nxt[i];if(i==1) break;
}
return false;
}
int main(){
int i,j;
scanf("%d",&n);
for(i=1;i<=n;++i){
scanf("%d",&j);
if(j>50) continue;
a[++m]=j;sum+=j;
}
n=m;sort(a+1,a+n+1);nxt[1]=1;
for(i=2;i<=n;++i) nxt[i]= a[i]==a[i-1]?nxt[i-1]:i;
for(len=a[n];len+len<=sum;++len)
if(sum%len==0){
cot=sum/len;vs[n]=1;
if(dfs(1,n,len-a[n])) break;
vs[n]=0;
}
if(len+len>sum) len=sum;
printf("%d\n",len);
return 0;
}