題目描述:http://poj.org/problem?id=1011
本題考察的內容是深度優先遍歷(DFS),要實現其要求的輸出並不困難。但是題目存在時間限制,僅僅使用DFS結果會超時。因此需要我們添加一些判斷拼湊成功和不成功的條件,也就是所謂的“剪枝”。
我在“剪枝”過程中遇到困難,查閱網絡資料後解決了問題。在這過程中,很大程度借鑑了這篇博客的內容,也可以看做是對其的解讀。
首先我們來看一下最開始在沒有“剪枝”的條件下的代碼:
<span style="font-size:10px;">import java.util.*;
public class Poj1011_uncut {
static boolean[] used;
static int num;//分割後木棍的數量
static int[] s;
static int sum;
static int max;
static int parts;
//"木棒"表示合成後的stick,"木棍"表示未合成的stick
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while((num = sc.nextInt()) != 0){
used = new boolean[num];
sum = 0;
s = new int[num];
for(int i = 0; i<num; i++){
s[i] = sc.nextInt();
sum += s[i];
}
Arrays.sort(s);//對木棍進行排序(從小到大)
max = s[num - 1];
//木棒的長度一定大於等於最長的木棍長度,所以從最長的木棍開始
for(; max<= sum; max++){
//木棒的長度一定能被總長度整除
if(sum%max == 0){
parts = sum/max;//木棒的數目
if(search(0, num - 1, 0)){
System.out.println(max);
break;
}
}
}
}
sc.close();
}
/*
* 搜索能拼成一個木棒的木棍
* @param res:當前這根木棒已有的長度
* @param next:下一個搜索的木棍的下標
* @param cpl:已經拼成的木棒數量
*/
public static boolean search(int res, int next, int cpl){
//當res=max時,本次合成成功。
if(res == max){
cpl++;
res = 0;
next = num - 2;
}
//cpl = parts,當前所有木棒合成完畢
if(cpl == parts){
return true;
}
//沒有成功,繼續合成
while(next >= 0){
//如果當前木棍沒有被用過
if(used[next] == false){
//木棒的當前長度+當前搜索的木棍長度 沒有超過max,則可以放入
if(res + s[next] <= max){
used[next] = true;
//繼續搜索成功
if(search(res + s[next], next - 1, cpl)){
return true;
}
//搜索不成功
used[next] = false;
}
}
next--;
}
return false;
}
}</span><span style="font-size: 14px;">
</span>
這樣的代碼是無法符合運行時間要求的。那麼我們接下來看看,有哪些條件是我們可以用來“剪枝”的。
第一類條件是正確解的充分條件:
1、木棒的長度一定大於等於最長的一根木棍的長度。
2、木棒的長度,一定是總長度的約數。
可以發現其實在上面爲通過的代碼中,也已經使用了這兩個條件。
第二類條件是非正確解的必要條件:
1、若某次搜索中,當前最長的木棍匹配失敗,這當前木棒的長度一定是非正確解。
2、若某次搜索中,當前木棍拼湊成功,但是剩下的無法拼湊成功,則此次匹配也一定失敗。
3、若當前剩餘未使用的木棍加起來都無法小於木棒的長度,則此次匹配也一定失敗。
還有第三類條件,可以使匹配更加高效:
1、如果某一個木棍匹配失敗,則在當前條件下,與其相同長度的木棍都不用在匹配了。
有了以上的條件,我們就可以對之前的代碼進行“剪枝”處理:
import java.util.*;
public class Poj1011_cut {
static boolean[] used;
static int num;//分割後木棍的數量
static int[] s;
static int sum;
static int max;
static int parts;
//"木棒"表示合成後的stick,"木棍"表示未合成的stick
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
while((num = sc.nextInt()) != 0){
used = new boolean[num];
sum = 0;
s = new int[num];
for(int i = 0; i<num; i++){
s[i] = sc.nextInt();
sum += s[i];
}
Arrays.sort(s);//對木棍進行排序(從小到大)
max = s[num - 1];
//木棒的長度一定大於等於最長的木棍長度,所以從最長的木棍開始
for(; max<= sum; max++){
//木棒的長度一定能被總長度整除
if(sum%max == 0){
parts = sum/max;//木棒的數目
if(search(0, num - 1, 0)){
System.out.println(max);
break;
}
}
}
}
sc.close();
}
/*
* 搜索能拼成一個木棒的木棍
* @param res:當前這根木棒已有的長度
* @param next:下一個搜索的木棍的下標
* @param cpl:已經拼成的木棒數量
*/
public static boolean search(int res, int next, int cpl){
//當res=max時,本次合成成功。
if(res == max){
cpl++;
res = 0;
next = num - 1;
//當一次合成成功後,next可能指在數組的任意位置,此時應該將其置回,繼續從當前最大的開始合成
}
//cpl = parts,當前所有木棒合成完畢
if(cpl == parts){
return true;
}
//沒有成功,繼續合成
while(next >= 0){
//如果當前木棍沒有被用過
if(used[next] == false){
//木棒的當前長度+當前搜索的木棍長度 沒有超過max,則可以放入
if(res + s[next] <= max){
used[next] = true;
//繼續搜索成功
if(search(res + s[next], next - 1, cpl)){
return true;
}
//搜索不成功
used[next] = false;
//若本次搜索失敗時,res=0,說明當前最長木棍無法合成木棒,則肯定會搜索失敗
if(res == 0){
break;
}
//可以合成當前的,但是剩下的無法合成
if(res + s[next] == max){
break;
}
}
//如果某一個木棍匹配失敗,則在當前條件下,與其相同長度的木棍都不用在匹配了
int i = next - 1;
while(i >=0 && s[i] == s[next]){
i--;
}
next = i;
//計算剩餘木棍的總長度
int l_s = 0;
while(i >= 0){
if(used[i] == false){
l_s += s[i];
}
i--;
}
//如果剩餘木棍的總長度都小於max-res,則拼湊一定失敗
if(l_s < max - res){
break;
}
continue;
}
next--;//此處用於控制當if(used[next] == false)不滿足時,指向下一個木棍
}
return false;
}
}