【題解】超經典的剪枝回溯——小木棍

題目描述

喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過50。現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。給出每段小木棍的長度,編程幫他找出原始木棍的最小可能長度。

輸入描述:

第一行爲一個單獨的整數N表示砍過以後的小木棍的總數。第二行爲N個用空格隔開的正整數,表示N根小木棍的長度。

輸出描述:

輸出僅一行,表示要求的原始木棍的最小可能長度。

示例1

輸入
9
5 2 1 5 2 1 5 2 1
輸出
6
備註:
1N601N601 \leq N \leq 601≤N≤60

思路1(先不考慮剪枝)

管原始木棍叫大棍,砍過以後的小木棍叫小棍以作區分好了。。。

總體採用枚舉所有可能解然後dfs搜索的思路。

枚舉:輸入每根小棍長度a[i]的時候記錄一下所有小棍的長度和sum。從最長的那根小棍a[0]開始直到長度和sum枚舉每一種可能的大棍長度。

dfs根據思路設計dfs參數當前的now號小棍開始,往後遍歷一遍所有小棍,將它們依次拼接上去。(每次都取剩餘小棍中最長的那根開始拼接,即先對所有小棍的長度a[n]進行一次排序)

若當前拼接的長度達到了我們枚舉假設出來的大棍長度 ,就進行下一根小棍的拼接

若當前小棍已經使用過則跳過這跟小棍。(需要一個visited數組記錄使用情況)

直到我們手裏的小棍全部用完&&正在拼接的這跟大棍剛剛好拼完 (即剩餘長度爲0)就返回true

綜上,我們dfs的參數需要有:當前準備拼接的now號小棍、這趟搜索假設出來的大棍長度len、記錄每次搜索時手裏剩下的小棍數量num、當前正在拼接的這根大棍的剩餘長度rest(參考上述思路中下劃線部分)

思路2(剪枝)

上述思想已經可以做出正確的答案了。但是不考慮剪枝的話這題會TLE。所以我們必須做出一些剪枝策略:

  1. 我們知道小棍是由n根大棍砍開得到的。由於這個n是一個正整數,所以大棍的長度一定是總長度的因子。即枚舉答案的時候,若當前枚舉出的數值不能被總長度整除,說明這不是一個可能的答案。直接跳過,都不需要進dfs了。
  2. 若將當前正在嘗試的這根小棍放置在大棍打頭的位置發現不可行,說明當前假設出的大棍長度是不可行的。因爲這根小棍總要被用上,而你放在第一個位置已經不可行了!(第一個位置的限制條件是最寬鬆的,因爲此時剩餘的長度最寬)放後面就更不可行了!此時直接結束dfs,不需要再試探下去了!
  3. 同理,若當前小棍放在大棍末尾的位置剛好能放下但後續的拼接無法恰好拼上,說明當前假設出來的大棍長度不可行(因爲這根小棍總要被放下!!!)跳過這個len
  4. 當前長度的小棍試探過後,與這一根相同長度的棍子就不用再試了。

ac代碼:

#include<bits/stdc++.h>
using namespace std;
int n;
int v[65], a[65];
bool cmp(int b, int c){
    return b > c;
}
bool dfs(int num, int rest, int len, int now){
    //剩下的小棍子數量,當前大棍剩下的長度
    //枚舉的大棍長度,拼接當前大棍的第now根小棍子
    if(0 == num && 0 == rest)  //小棍子全部拼完且沒有剩餘長度
        return true;
    if(0 == rest){             //拼下一根大棍
        rest = len;
        now = 0;
    }
    for(int i = now; i < n; i++){
        if(v[i]) continue;	   //已使用
        if(a[i] > rest) continue;//這根小棍大於剩餘長度,放不下,pass
        v[i] = 1;
        if(dfs(num - 1, rest - a[i], len, i))
            return true;
        v[i] = 0;//回溯
        if(rest == len || rest == a[i])
            break;             //第一根或最後一根小棍不行,說明當前枚舉的len行不通
        while(a[i] == a[i + 1])
            i++;               //與當前長度一致的小棍就不需要再嘗試了
    }
    return false;
}
int main(){
    cin>>n;
    int sum = 0, i;
    for(i = 0; i < n; i++){
        cin>>a[i];
        sum += a[i];
    }
    sort(a, a + n, cmp);         //從最長的小棍子開始拼接
    for(i = a[0]; i <= sum; i++){//從最長的小棍長度開始枚舉
        if(sum % i != 0) continue;//假設的大棍不能被總長整除,不可行!
        if(dfs(n, i, i, 0)) break;
    }
    cout<<i<<endl;
    return 0;
}

就是這樣。BTW:dfs不要忘記回溯:恢復當前小棍未使用的狀態

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章