一、引子
剪枝,就是減小搜索樹規模、儘早排除搜索樹中不必要的分支的一種手段。
形象地看,就好像剪掉了搜索樹的枝條,故被稱爲剪枝。
二、常見剪枝方法
1.優化搜索順序
在一些問題中,搜索樹的各個分支之間的順序是不固定的
不同的搜索順序會產生不同的搜索形態,規模也相差甚遠
2.排除等效分支
在搜索過程中,如果我們能夠得知搜索樹的當前節點沿着
某幾條不同分支到達的子樹是等效的,那麼只需要對其中
一條路徑進行搜索
3.是否可行剪枝
在搜索過程中,每次對當前狀態進行檢查,如果發現不可
能到達遞歸邊界,就執行回溯
4.最優性剪枝
在求解最優解的過程中,如果當前解已經沒有當前最優解
優秀,此時可以執行回溯語句
5.記憶化剪枝
記錄每個狀態的搜索結果,在重複遍歷一個狀態時返回
三、例題
(一)生日蛋糕 POJ1190
題目描述
Mr.W 要製作一個體積爲Nπ 的 M層生日蛋糕,每層都是一個圓柱體。設從下往上數第 i蛋糕是半徑爲 Ri,高度爲 Hi 的圓柱。當 i<Mi<M 時,要求Ri>Ri+1且Hi>Hi+1。由於要在蛋糕上抹奶油,爲儘可能節約經費,我們希望蛋糕外表面(最下一層的下底面除外)的面積 Q最小。 令 Q=Sπ ,請編程對給出的 N和M ,找出蛋糕的製作方案(適當的 Ri 和Hi 的值),使 S最小。(除 QQ 外,以上所有數據皆爲正整數)
輸入
第一行爲 N ,表示待制作的蛋糕的體積爲Nπ;
第二行爲 M ,表示蛋糕的層數爲 M 。
輸出
輸出僅一行,一個整數 S(若無解則 S=0 )。
樣例輸入
100 2
樣例輸出
68
提示
附:圓柱相關公式:體積 V=πR2H;側面積 S’=2πRH;底面積 S=SπR2。
數據範圍與提示
對於全部數據,1≤N≤104,1≤M≤20。
題解:
搜索框架:從下往上搜索,枚舉每層的半徑和高度作爲分支
搜索狀態:第dep層,當前外表面積s,當前體積v,dep+1層的高度和半徑
整個蛋糕的底面積=最底層的圓面積,這樣在m-1層搜索時,只需要計算側面積
剪枝:
1.是否可行剪枝
首先枚舉
其次枚舉
2.優化搜索順序
倒序搜索枚舉
3.可行性剪枝
預處理最小體積和側面積,然後剪枝
#include <bits/stdc++.h>
using namespace std;
int n,m;
int ans=2e9;
void dfs(int dep,int s,int v,int past_r,int past_h)
{
if(s >= ans) return;
if(dep == m+1 && v == n)
{
ans = min(ans,s);
return;
}
if(v >= n) return;
int rest_dep = m - dep + 1;
if(rest_dep * past_r * past_r * past_h + v < n) return;
if(dep == 1)
{
for(int r = past_r; r >= m; --r)
for(int h = m; h <= past_h; ++h)
dfs(dep + 1,s + r * r + 2 * r * h,v + r * r * h,r,h);
}
else
{
for(int r=past_r-1; r>=m-dep+1; --r)
for(int h=m-dep+1; h<past_h; ++h)
dfs(dep + 1,s + 2 * r * h,v + r * r * h,r,h);
}
}
int main()
{
scanf("%d%d",&n,&m);
dfs(1,0,0,28,28);
if(ans == 2e9) printf("0\n");
else printf("%d\n",ans);
}
(二)Sticks POJ1011
題目描述
喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過 50。現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。給出每段小木棍的長度,編程幫他找出原始木棍的最小可能長度。
輸入
第一行爲一個單獨的整數N表示砍過以後的小木棍的總數。 第二行爲 N個用空格隔開的正整數,表示N根小木棍的長度。
輸出
輸出僅一行,表示要求的原始木棍的最小可能長度。
樣例輸入
9 5 2 1 5 2 1 5 2 1
樣例輸出
6
提示
數據範圍與提示
1≤N≤60
題解:
我們可以從大到小枚舉原始木棒的長度len.而len應該是sum的約數
原始木棒的根數cnt=sum/len
對於枚舉的每個len,我們可以依次搜索每根原始木棒由哪些木棍組成
搜索順序:從大到小
搜索狀態:正在拼的木棍序號、木棒長度、現在拼好的長度、現在拼好的木棒根數
剪枝
1.優化搜索順序
從大到小排序,優先嚐試長的木棍
2.排除等效分支
(1).因爲先拼一根a長度的,再拼一根b長度的等於
先拼一根b長度的,再拼一根a長度,只要搜索其中一種
(2).記錄最近一次嘗試拼接的木棍長度,如果搜索失敗則不嘗試
同種長度的木棍
(3).當嘗試拼入的第一根木棍就返回失敗,則直接回溯
(4).如果當前木棍拼入後,一節木棒被填充完整,而另一根木棍
失敗了,則直接回溯
代碼:
#include<bits/stdc++.h>
#define MAXN 100005
using namespace std;
int n,sum=0,a[MAXN];
bool vis[MAXN];
int cmp(int a,int b)
{
return a>b;
}
bool dfs(int num,int len,int now_len,int number)
{
if(number==sum/len)return 1;
if(now_len==len)
{
if(dfs(1,len,0,number+1))return 1;
}
for(int i=num; i<=n; i++)
{
if(!vis[i]&&now_len+a[i]<=len)
{
vis[i]=true;
if(dfs(i+1,len,now_len+a[i],number))return 1;
vis[i]=false;
if(a[i]==len-now_len||now_len==0)break;
while(a[i]==a[i+1])i++;
}
}
return 0;
}
int main()
{
memset(vis,false,sizeof(vis));
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
sum+=a[i];
}
sort(a+1,a+1+n,cmp);
for(int i=a[1]; i<=sum; i++)
{
if(sum%i!=0)continue;
if(dfs(1,i,0,0))
{
cout<<i<<endl;
break;
}
}
}