問題描述:
喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過50。
現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。
給出每段小木棍的長度,編程幫他找出原始木棍的最小可能長度。
AC代碼:
int sum_of_sticks;//大木棍的根數
int length_of_sticks;//大木棍的長度
int sum_of_length = 0,max_of_length = -inf;//長度的總和、小木棍中長度的最大值
int size_of_length;//length數組的大小
int length[70];
int sum[70];//後綴和
bool book[70];//標記數組
bool stop;
bool cmp(const int a,const int b)//按照木棍長度從大到小排序
{
return a > b;
}
//已經拼成的木棍數、正在拼的木棍已經拼了多少
void dfs(int success_of_stick,int accumulation_of_stick,int prev)
{
if(stop == true)
return;
//如果正在拼的木棍累計的長度等於期望中的大木棍的長度
if(accumulation_of_stick == length_of_sticks)
{
success_of_stick++;//累加
accumulation_of_stick = 0;//清零
prev = 1;//從第一根小木棍開始搜
}
//如果已經拼成了期望中的大木棍數
if(success_of_stick == sum_of_sticks - 1)
{
printf("%d\n",length_of_sticks);//輸出答案
stop = true;//終止搜索
return;
}
//對於每一根小木棍
for(int i = prev; i <= size_of_length; ++i)//剪枝③
{
if(book[i] == true)//如果該小木棍沒被用過,剪枝④
continue;
//如果粘上後的長度超過了期望中的大木棍的長度,剪枝⑤
if(accumulation_of_stick + length[i] > length_of_sticks)
continue;//那自然不合題意
//如果當前的小木棍和上一個小木棍一樣長並且上一個小木棍沒有被採用,剪枝⑥
if(length[i] == length[i - 1] && book[i - 1] == false)
continue;//那必須是一樣的下場
if(accumulation_of_stick + sum[i] < length_of_sticks)//剪枝剪枝⑦
return;
book[i] = true;//標記,核心代碼
dfs(success_of_stick,accumulation_of_stick + length[i],i + 1);//核心代碼
book[i] = false;//取消標記,核心代碼
if(accumulation_of_stick == 0)//剪枝⑧沒看懂
return;
if(accumulation_of_stick + length[i] == length_of_sticks)//剪枝⑨沒看懂
return;
}
return;
}
void init()
{
int n;
cin >> n;//小木棍的根數
int a,b = 1;//濾去無效數據
for(int i = 1; i <= n; ++i)
{
cin >> a;
if(a <= 50)
{
length[b] = a;
b++;
}
}
size_of_length = b - 1;//木棍的根數
for(int i = 1; i <= size_of_length; ++i)
{
sum_of_length += length[i];//長度的總和
max_of_length = max(max_of_length,length[i]);//長度的最大值
}
for(int i = size_of_length; i >= 1; --i)//後綴和
sum[i] = sum[i + 1] + length[i];
return;
}
int main()
{
init();
//排序,剪枝①
sort(length + 1,length + size_of_length + 1,cmp);
// 注:此段代碼也能過,而且不慢!
// 因爲題目要求原始長度最小
// 又因爲:長度的總和=原始長度*原始根數
// 即原始根數最多,於是由大到小枚舉
// for(int i = size_of_length; i >= 1; --i)
// {
// //如果長度的總和能整除原始的根數
// //可能是解,搜搜試試
// if(sum_of_length % i == 0)
// {
// sum_of_sticks = i;
// length_of_sticks = sum_of_length / i;
// dfs(0,0,1);
// }
// }
//因爲題目要求原始長度最小
//所以直接從較小的原始長度開始枚舉
//原始長度最小也要是最長的小木棍的長度
//因爲小木棍是由原始木棍生成的,不能越砍越長不是麼
//上限又是什麼呢,難道是木棍長度的總和嗎
//即一根木棍的情況,剪枝②
for(int i = max_of_length; i <= sum_of_length / 2; ++i)
{
//如果長度的總和能整除原始的根數
//可能是解,搜搜試試
if(sum_of_length % i == 0)
{
sum_of_sticks = sum_of_length / i;
length_of_sticks = i;
dfs(0,0,1);
}
}
if(stop == false)
printf("%d\n",sum_of_length);
return 0;
}
解決方法:
初始時,每根木棍的長度都是相同的!
搜索也就是把小木棍用502粘起來,暴力地去試,不行就再掰斷。
搜索的參數(或者說狀態)可能比較難設計。不過也不要緊,先畫出狀態轉移樹,仔細考慮考慮
以下是深度優先搜索的一個模板:
void dfs(答案,搜索層數,其他參數)
{
if(搜索層數 == max_deep)
{
更新答案;
return;
}
(剪枝;)
for(枚舉下一層可能的狀態)
{
更新全局變量表示狀態的變量;
dfs(答案+新狀態增加的價值,層數 + 1,其他參數);
還原全局變量表示狀態的變量;
}
return;
}
小知識:
exit()通常是用在子程序中用來終結程序用的,使用後程序自動結束,跳回操作系統。
exit(0) 表示程序正常退出,exit⑴/exit(-1)表示程序異常退出。
if(accumulation_of_stick == length_of_sticks)
{
success_of_stick++;//累加
accumulation_of_stick = 0;//清零
}
//如果已經拼成了期望中的大木棍數
if(success_of_stick == sum_of_sticks)//因爲必須要先進行累加、清零,再進行這一步的判斷!!!
{
printf("%d\n",length_of_sticks);//輸出答案
stop = true;//終止搜索
return;
}
以上代碼順序不能顛倒!!!爲什麼!!!
DFS_ONE()//噗,有毒啊
{
for(int i = 1; i <= n; ++i)
DFS_TWO();
}
對剪枝的解釋:
剪枝①:排序對剪枝⑥有幫助
剪枝②:因爲做一次搜索的時間開銷不少,所以我們要想辦法使得搜索的次數越少越好。我們必須儘量縮小for循環中i的範圍,i的下限註釋中講得很清楚了。爲什麼上限是長度的總和除以2呢?按理來說應該是長度的總和吧。我們把原始木棍的根數分爲一根和兩根及以上的情況。因爲是兩種情況,所以非此即彼。現在我們明白了吧,對於兩根及以上的情況,上限就是長度的總和除以2,而如果答案是一根的情況呢,程序的最後一句也有判斷。剪枝效果就是少搜索了一次
剪枝③:從前向後搜,前面已經考慮過的就不再去管它
剪枝④:常規
剪枝⑤:常規
剪枝⑥:這個剪枝有點意思
剪枝⑦:如果當前的湊出來的長度加上後面所有長度的總和都湊不出一根木棍了,這一枝就剪掉
剪枝⑧:
剪枝⑨:
參考資料,大佬秀得我頭疼