木棒拼接,POJ上的1011
題目:http://poj.org/problem?id=1011
經典的搜索+剪枝問題。這題我已經做第三遍了,感覺依舊很暈。說一下,自己對搜索的感覺。以前寫搜索算法,總會不自覺的用到棧。比如,迷宮問題,通過壓棧保存路徑,通過彈棧進行回退。還要設置個標誌,保證“不走來時路”。
看了大牛們寫的code後,有種醍醐灌頂的感覺:遞歸形式的寫法真簡潔!因爲這種寫法把壓棧彈棧的工具交給了complier, 所以代碼顯得格外清爽。但是,玫瑰雖香,身上帶刺。清爽簡潔並不代表簡單易寫。這是,函數的參數、返回值都要反覆斟酌。要不斷的問自己:函數需要幾個參數、有沒有返回值、遞歸終止條件是什麼。
針對本題,拼接的過程是一個DFS,即:不斷拿木棒嘗試,拼好一個再拼下一個。需要知道的參數:當前拼接已獲得的木棒長度、已經拼接成功的木棒數、當前正在嘗試的木棒編號。拼接的過程需要判斷,所以函數返回bool.
在用棧實現時,回退就是不斷的彈棧。這裏,回退就比較隱晦,只是把已經設置的標記再復原一下。而且,遞歸調用時也很抽象。只有像binary tree上寫遞歸一樣,假設遞歸調用的部分結果已知,然後才能繼續寫後面的代碼。
本題的另一個關鍵在剪枝,可以用於剪枝的條件:
1. 對木棍長度按從大到小排序。
2. 計算所有木棍的總長度,只搜索能被總長度整除的數
3. 從木棍的最大長度開始搜索
4. 保存上次嘗試的木棒長度,如果拼接失敗,則相同長度不用重複嘗試
5. 當嘗試的木棒標號回退到0時,直接返回false
Problem: 1011 User: 3109034010
Memory: 264K Time: 16MS
Language: C++ Result: Accepted
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
//***********************常量定義*****************************
const int MAX_NUM = 70;
//*********************自定義數據結構*************************
//********************題目描述中的變量************************
int stickNum;
int stickLen[MAX_NUM];
//**********************算法中的變量**************************
int sumLen;
bool used[MAX_NUM];
//***********************算法實現*****************************
bool FindStick( int curId, int curLen, int curNum, int targetLen )
{
if( curNum == sumLen / targetLen )
{
return true;
}
if( curLen == targetLen )
{
return FindStick( 0, 0, curNum+1, targetLen );
}
else
{
int i;
int pre = 0;
for( i=curId; i<stickNum; i++ )
{
if( pre != stickLen[i] && ( curLen + stickLen[i] <= targetLen ) && !used[i] )
{
pre = stickLen[i];
used[i] = true;
//下面這一句很抽象
//當FindStick == true時, stickLen[i]和後續的木棒都可以拼接成功
if( FindStick( i+1, curLen + stickLen[i], curNum, targetLen ) )
{
break;
}
used[i] = false;
//這句也不好理解
//爲什麼不是 i == 0
if( curId == 0 )
{
return false;
}
}
}
if( i >= stickNum )
return false;
else
return true;
}
}
//************************main函數****************************
int main()
{
//freopen( "in.txt", "r", stdin );
while( cin >> stickNum, stickNum )
{
sumLen = 0;
memset( used, false, sizeof(used) );
for( int i=0; i<stickNum; i++ )
{
cin >> stickLen[i];
sumLen += stickLen[i];
}
sort( stickLen, stickLen + stickNum, greater<int>() );
int x;
for( x=stickLen[0]; x<sumLen; x++ )
{
if( sumLen % x == 0 )
{
if( FindStick( 0, 0, 0, x ) )
{
break;
}
}
}
cout << x << endl;
}
return 0;
}