剪枝總結

一、引子

剪枝,就是減小搜索樹規模、儘早排除搜索樹中不必要的分支的一種手段。

形象地看,就好像剪掉了搜索樹的枝條,故被稱爲剪枝。

二、常見剪枝方法

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.是否可行剪枝

首先枚舉R\epsilon [dep,min(\sqrt{N-v},r[dep+1]-1)]

其次枚舉H\epsilon [dep,min(\sqrt{N-v}/R^{2},h[dep+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;
        }
    }
}

 

 

發佈了57 篇原創文章 · 獲贊 12 · 訪問量 4820
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章