本題實質是一個單純的DFS,而且也不用剪枝,直接暴搜 + 枚舉即可。仔細分析本題,可以對遞歸算法的實現有更深的理解。特別能幫助理解,遞歸算法層層深入,然後可能回溯返回的過程。如何保存遞歸路徑也是經常會遇到的問題。
顯然,當取到最優解的時候,紙片被分成若干段。並且,這若干段的和與目標數字最接近。因此,求最優解的過程就是依次確定第一個切割位置、第二個切割位置等等。
首先決定第一次切割的位置,這時紙片被分成兩個部分,計算當前兩部分數字和。如果滿足條件,則記錄當前結果。然後,對第一次切割剩下的右半部分,再尋找第二次切割的位置。依次下去,直到分割位置到達字符串末尾。因爲在每一次分割中可能有多個可選的位置,我們當前只使用了第一個,所以我們需要回溯。也就是,從下一層回到當前層,嘗試當前層的下一個可能位置。回溯的另一個方面表現在分割路徑的記錄。
題目要求打印具體的分割情況,所以遞歸求解的過程需要記錄每次的分割位置。每當取得一個可行解的時候,記錄可行解的分割路徑。當下一個可行解比當前解更優時,替換分割路徑。
這裏需要注意一下:有些情況,可能根本不分割就可以取得最有解,如10, 9。
#include <iostream>
#include <string>
#include <vector>
using namespace std;
//***********************常量定義*****************************
const int INF = 999999999;
//*********************自定義數據結構*************************
//********************題目描述中的變量************************
int target;
string strValue;
//**********************算法中的變量**************************
int ans = INF;
//保存方案的當前分割點
vector<int> curPath;
//保存可行方案的分割點
vector<int> path;
//標記解是否唯一
bool notOnlySolve;
//***********************算法實現*****************************
//target爲目標數,strValue爲紙條上的數
//startPos爲當前搜索的起始位置
//curSum爲當前已分割開的數字的總和
bool Search( int target, string strValue, int startPos, int curSum )
{
int iSize = (int)strValue.size();
//從起始位置開始搜索,並枚舉所有分割點
for( int i=startPos; i<iSize-1; i++ )
{
curPath.push_back( i );
int iLeft = atoi( ( strValue.substr( startPos, i-startPos+1 ) ).c_str() );
int iRight = atoi( ( strValue.substr( i+1, (iSize-1)-(i+1)+1 ) ).c_str() );
int sum = curSum + iLeft + iRight;
//如果分割方案可行
if( sum <= target && target - sum <= ans )
{
//如果與以前某個方案的結果相同
if( target - sum == ans )
notOnlySolve = true;
else
{
notOnlySolve = false;
ans = target - sum;
//保存可行方案的分割點
path.clear();
path.insert( path.begin(), curPath.begin(), curPath.end() );
}
}
//DFS遞歸調用
Search( target, strValue, i+1, curSum+iLeft );
//回溯,在當前搜索深度下,枚舉下一個分割方案
curPath.pop_back();
}
if( ans == INF || notOnlySolve )
return false;
else
return true;
}
void PrintPath()
{
if( path.empty() ) return;
int i, j;
for( i=0; i<=path[0]; i++ )
{
cout << strValue[i];
}
cout << " ";
int size = (int)path.size();
for( i=1; i<size; i++ )
{
for( j=path[i-1]+1; j<=path[i]; j++ )
{
cout << strValue[j];
}
cout << " ";
}
for( j=path[size-1]+1; j<strValue.size(); j++ )
{
cout << strValue[j];
}
cout << endl;
}
//************************main函數****************************
int main()
{
freopen( "in.txt", "r", stdin );
while( cin >> target >> strValue, target != 0 && strValue != "0" )
{
//如果紙條上的數和目標數相等
int iValue = atoi( strValue.c_str() );
if( target == iValue )
{
cout << target << " " << target << endl;
}
else
{
//復原全局變量
notOnlySolve = false;
path.clear();
curPath.clear();
ans = INF;
// 如果有解且唯一
if( Search( target, strValue, 0, 0 ) )
{
//如果不分割
if( ( target > iValue ) && ( target - iValue <= ans ) )
cout << iValue << " " << iValue << endl;
else
{
cout << target - ans << " ";
PrintPath();
}
}
else
{
//解不唯一
if( notOnlySolve )
cout << "rejected" << endl;
//無解
else
{
if( ( target > iValue ) && ( target - iValue <= ans ) )
cout << iValue << " " << iValue << endl;
else
cout << "error" << endl;
}
}
}
}
return 0;
}