洛谷P1120 小木棍 [資料加強版]【搜尋+毒瘤剪枝】
時空限制 300ms-1000ms / 128MB
題目描述
喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過50。 現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。 給出每段小木棍的長度,程式設計幫他找出原始木棍的最小可能長度。
輸入格式:
共二行。 第一行為一個單獨的整數N表示砍過以後的小木棍的總數,其中N≤65 (管理員注:要把超過50的長度自覺過濾掉,坑了很多人了!) 第二行為N個用空個隔開的正整數,表示N根小木棍的長度。
輸出格式:
一個數,表示要求的原始木棍的最小可能長度
說明
-#17 #20 #22 #27 四組資料時限500ms -#21 #24 #28 #29 #30五組資料時限1000ms 其他時限改為200ms
題目分析
練習搜尋剪枝的絕世好(du)題(liu) 首先確定搜尋思路在逐步加入剪枝
我們可以列舉原來每段木棍的長度len來搜尋判斷是否可行 先計算出木棍總長,顯然一定是的約數 確定了的同時,我們也確定了原來木棍個數
for(len=mx;len<=sum;++len)//mx時切割後所有小木棍中最長的那個的長度
{
if(sum%len) continue;
cnt=sum/len;
memset(vis,0,sizeof(vis));
if (dfs(1,0)) break;
}
首先最簡單最暴力的搜尋,可得27pts
int dfs(int k,int m)//當前以拼完k-1個木棍,正在拼第k個,且第k個木棍當前長m
{
if(k==cnt+1) return 1;//拼完所有cnt個,方案合法
if(m==len) return dfs(k+1,0);//拼完了第k個,開始拼下一個
for(int i=1;i<=n;++i)
{
if(vis[i]||m+a[i]>len) continue;
vis[i]=1;//嘗試將第i個小木棍拼上去
if(dfs (k,m+a[i])) return 1;
vis[i]=0; //回溯後記得重置
}
return 0;
}
現在開始考慮剪枝 1.一個最明顯的剪枝就是一開始小木棍從大到小排序 這一點比較簡單不解釋了,排序後33pts
2.若當前木棍還沒拼完,那麼繼續搜尋拼接這個木棍的小木棍時 前面已列舉過的顯然不用再考慮
剪枝後39pts
int dfs(int k,int m,int last)//last記錄上一次列舉到的位置
{
if(k==cnt+1) return 1;
if(m==len) return dfs(k+1,0,1);
for(int i=last;i<=n;++i)//從last開始列舉
{
if(vis[i]||m+a[i]>len) continue;
vis[i]=1;
if(dfs(k,m+a[i],i+1)) return 1;
vis[i]=0;
}
return 0;
}
3.假如已嘗試將某個長度為的小木棍拼接到當前木棍不可行 那麼相同長度的木棍不用再搜尋
剪枝後54pts
int dfs(int k,int m,int last)
{
if(k==cnt+1) return 1;
if(m==len) return dfs(k+1,0,1);
int pre=0;//記錄上一次用的長度
for(int i=last;i<=n;++i)
{
if(vis[i]||m+a[i]>len||a[i]==pre) continue;//計入a[i]==pre,相同則跳過的剪枝
vis[i]=1;
if(dfs(k,m+a[i],i+1)) return 1;
vis[i]=0; pre=a[i]; //到達這裡說明ai不可行,用pre記錄
}
return 0;
}
4.假設當前正在拼接的木棍長度為0 且第一次嘗試將某個小木棍拼接不可行後,直接回溯
因為若當前小木棍長度為0,說明剩餘沒拼的木棍都是等價的(都是0) 若把放入當前木棍不可行,那麼放入其他等價的木棍顯然也不可行
剪枝後78pts
int dfs(int k,int m,int last)
{
if(k==cnt+1) return 1;
if(m==len) return dfs(k+1,0,1);
int pre=0;
for(int i=last;i<=n;++i)
{
if(vis[i]||m+a[i]>len||a[i]==pre) continue;
vis[i]=1;
if(dfs(k,m+a[i],i+1)) return 1;
vis[i]=0; pre=a[i];
if(m==0) return 0;//若第一次搜尋就不成功,直接回溯
}
return 0;
}
5.若當前正在拼接的木棍長度+當前列舉的小木棍長度== 且此次向下搜尋判定為不合法,則可以直接回溯
因為如果繼續列舉,用兩個更小的木棍湊出,顯然與上述也是等價的
剪枝後100pts
int dfs(int k,int m,int last)
{
if(k==cnt+1) return 1;
if(m==len) return dfs(k+1,0,1);
int pre=0;
for(int i=last;i<=n;++i)
{
if(vis[i]||m+a[i]>len||a[i]==pre) continue;
vis[i]=1;
if(dfs(k,m+a[i],i+1)) return 1;
vis[i]=0; pre=a[i];
if(m==0||m+a[i]==len) return 0;
}
return 0;
}
完整程式碼
#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return x*f;
}
const int maxn=100;
int n,a[maxn],ans;
int vis[maxn],stick[maxn];
int sum,mx,cnt,len;
bool cmp(int a,int b){return a>b;}
int dfs(int k,int m,int last)
{
if(k==cnt+1) return 1;
if(m==len) return dfs(k+1,0,1);
int pre=0;
for(int i=last;i<=n;++i)
{
if(vis[i]||m+a[i]>len||a[i]==pre) continue;
vis[i]=1;
if(dfs(k,m+a[i],i+1)) return 1;
vis[i]=0; pre=a[i];
if(m==0||m+a[i]==len) return 0;
}
return 0;
}
int main()
{
n=read();
for(int i=1;i<=n;++i)
{
int x=read();
if(x>50) continue;
a[++cnt]=x;
mx=max(mx,a[cnt]); sum+=a[cnt];
}
n=cnt; cnt=0; sort(a+1,a+1+n,cmp);
for(len=mx;len<=sum;++len)
{
if(sum%len) continue;
cnt=sum/len;
memset(vis,0,sizeof(vis));
if(dfs(1,0,1)) break;
}
printf("%d",len);
return 0;
}