【luogu】【DFS+神剪枝】小木棍(数据加强)
题目描述
乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过50。
现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。
输入输出格式
输入格式:
输入文件共有二行。
第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65
(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)
第二行为N个用空个隔开的正整数,表示N根小木棍的长度。
输出格式:
输出文件仅一行,表示要求的原始木棍的最小可能长度
代码
普通DFS不加last剪枝=44分
加last剪枝=54分
加神TM剪枝=100分
蛤蛤
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <functional>
#define maxn 520
#define INF 100000000
using namespace std;
int n,L,last=1,s[maxn],suml=0,maxl=0,minl=INF,c=0;
bool vis[maxn];
bool dfs(int cnt,int len){//剩余cnt根木棍,拼到当前还差len的长度
if(cnt==0&&len==0) return 1;//木棍不剩且刚好拼到目标长度就得到答案
int x=1;//初始化木棍从最长的开始试拼
if(len==0) len=L;//如果上一次刚好拼完一整根L,归零重新开始拼
if(len!=L) x=last;//last保存上一次是用s[last]这根木棍来减少len的,那么本次搜索就不必选比s[last]更长的木棍
//这是因为在尝试拼这一根木棍的时候,s[last]以前的都已经尝试过了
//如果说已经开始拼新的一根木棍了,就要从头开始搜没用过的了
for(int i=x;i<=n;i++){
if(vis[i]||s[i]>len) continue;//如果已经被占用,跳过
if(i>=1&&!vis[i-1]&&s[i]==s[i-1]) continue;//如果相同长度的不能用,跳过
vis[i]=1; last=i;
if(dfs(cnt-1,len-s[i])) return 1;
vis[i]=0;
if(len==L||s[i]==len) return 0;//这步很机智啊,如果开始拼新木棍了,那么任何没有被选过的木棍都要能被选进去才行
//如果有一根木棍刚好可以填满,之后却爆炸了,那肯定也没解
//加上这个剪枝瞬间从50分到100分啊...
}
return 0;
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&n);
int temp;
for(int i=1;i<=n;i++) {
scanf("%d",&temp);
if(temp>50) continue;
else {
s[++c]=temp; suml+=s[c];
}
} n=c;
sort(s+1,s+1+n,greater<int>());
for(L=s[1];L<=suml/2;L++){
if(suml%L!=0) continue;//如果目标长度不是sum的因数就可以跳过了
memset(vis,0,sizeof(vis));
if(dfs(n,L)){
printf("%d",L);
break;
}
}
if(L>suml/2) printf("%d",suml);
return 0;
}