10020 一本通 1 3 例 3 小木棍

2021-09-05 06:37:39 字數 2912 閱讀 5727

【題目描述】

原題來自:cerc 1995

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

【輸入格式】

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

【輸出格式】

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

【樣例輸入】

9

5 2 1 5 2 1 5 2 1

【樣例輸出】

6
【資料範圍與提示】1≤n≤60

這道題是一道很經典很經典的遞迴題,因為這道題當中用到的回溯絕對是扛扛的,反倒我覺得這道題的剪枝不特別特別的多,多的只是回溯,難的也是回溯,回溯到上一次的遞迴,我花了好多時間才理解清楚遞迴當中

接下來回到這個最正規的思路:

從最優性方面:

設所有木棍長度和為maxn,那麼原長度(也就是需要輸出的長度)一定能夠被maxn整除,這樣得到的木棍根數才是整數

木棍原來的長度一定不小於所有木棍中最長的那根

綜上兩點,可以確定原木棍的長度len在最長木棍的長度minx和maxn之間取值,且maxn能被len整除。所以在搜尋原木棍的長度時,可以從砍過以後所有木棍中最長的長度開始,每次增加長度後,必須能整除maxn。這樣可以有效優化程式。

從可行性方面:

短木棍更加靈活,長木棍受到的限制更大,所以可以對輸入的所有木棍按長度從大到小排序。

在砍斷後的排好序的木棍中,當用木棍i拼合原始木棍時,可以從i+1的木棍開始往後搜,因為i前面的木棍已經用過了

從當前最長長度的木棍開始搜,如果拼不出當前設定的原木棍長度len則直接返回,換乙個原始木棍長度len

相同長度的木棍不要搜尋多次.用當前長度的木棍搜下去得不出結果時,用一支同樣長度的還是得不到結果,所以可以提前返回

判斷搜到的幾根木棍組成的長度是否大於原始長度len,如果大於沒必要搜下去,可以提前返回

判斷當前剩下的木棍根數是否夠拼成木棍,如果不夠,肯定拼合不成功,直接返回

找到結果後,在能返回的地方馬上返回到上一層的遞迴處

從總的思路上:

標準回溯一定要有

木棍的區間要確定

所有木棍長度一定可以被len整除

幾個剪枝要知道

這道題想要ac還要加上二分,來節省搜尋時間

思路大概就是我上面所說的,剩下的細節看**的解釋吧。 

#include#include#includeusing namespace std;

int n,d,a[70],cnt,minx,maxn,len,nxt[70],m;

//a[i]是用來記錄每個標號(cnt)所代表的數 //minx和maxn分別表示下限和上限 //len表示的是我們的區間中的值

//nxt[i]表示的就是記錄當前相同的數的版塊 //m表示我們預估的這個原來的根數

bool vis[70],flag;//vis[i]判斷i是否用過(1表示用過的(true),0表示沒用過的(false)) //flag表示我們要找到的目標

bool cmp(int x,int y)

void dfs(int k,int last,int res)

int i;

for(i=1;i<=cnt;i++)//又找到乙個還沒用過的木棍

if(vis[i]==false) break;//如果我們判斷這是沒用過的就退出這個迴圈,進入到下面

vis[i]=true;//定義為用過的(占用資源)

dfs(k+1,i,len-a[i]);//用它拼接

vis[i]=false;//拼接完之後就又可以用了(釋放資源)

if(flag==1) return;//找到答案就可以層層退出(也就是返回上一層遞迴)

} int l=last+1,r=cnt,mid;

while(l>1;//等於:mid=(l+r)/2;

if(a[mid]<=res) r=mid;//如果小於我們所需要的就要往左邊找,因為我們的a陣列是排序過從大到小

else l=mid+1;//否則往右邊找,排序過的從大到小

} for(int i=l;i<=cnt;i++)/*從l開始縮短搜尋範圍,之間在這個還需要的長度的區間當中找合適的,

由於所有木棍是按從大到小的順序排的,因此從上述找到的木棍向右列舉*/ }

return;

}int main()

} sort(a+1,a+cnt+1,cmp);/*把所選的小木棍從長到短排序,從長的開始選擇,因為短的靈活好用*/

nxt[cnt]=cnt;/*記錄當前有多少個相同的數的版塊只,能記錄就是nxt[cnt]=cnt

所以剛開始只有最後乙個是成立的*/

for(int i=cnt-1;i>0;i--)//cnt是我們擁有的個數,所以我們的這個版塊的排序是從小到大,而不是從大到小

for(len=minx;len<=maxn/2;len++)/*長度最小就是我們定義的下線,

最大也不可能超過總和的一半,因為至少要分成兩組啊*/

}} printf("%d\n",maxn);/*如果找不到這個區間當中的len的話,

就直接輸出全部長度,也就是只有乙個木棍*/

return 0;

}

大概就是這樣了,回溯什麼的是這道題當中最難的

一本通 1633 例 3 Sumdiv

今天早上考試考了這道題 題意 求 a 所有約數之和 9901的結果。思路 暴力 快速冪 線性判約數再求和,30分。正解 看到求約數之和,很自然想到唯一分解定理,對於正整數n,n a 1a 2 dots a n 而言,它的約數之和為 1 a 1 a 1 2 dots a 1 1 a 2 a 22 do...

一本通1587 例 3 Windy 數

時間限制 1000 ms 記憶體限制 524288 kb 題目描述 原題來自 scoi 2009 windy 定義了一種 windy 數 不含前導零且相鄰兩個數字之差至少為 2 的正整數被稱為 windy 數。windy 想知道,在 a 和 b 之間,包括 a和 b,總共有多少個 windy 數?輸...

一本通 1 2 例 3 曲線

題目link 經典的三分裸題。三分主要是用來求乙個滿足單峰性的函式的最大 最小值的一種演算法,其原理和二分基本一樣。假設求最小值,首先把選擇區域分為三段,然後比較這兩個三等分點的函式值誰更小一些,大的那一邊就不要了 如果大的是靠左的,那就連著左邊不要了,靠右同理 容易證明這樣做是正確的,然後像二分那...