P1120 小木棍 資料加強版

2022-02-17 16:42:55 字數 3449 閱讀 2056

原題鏈結  

不得不說,這題真的是一道深度搜尋毒瘤題qwq,整整坑了我乙個上午。

先說一下大體思路:

1.讀入資料的同時(注意過濾掉長度大於50的木棍),算出所有小木棍的總和sum,因為至少所有的小木棍會拼成一根長度為sum的超大木棍;

2.找出所有小木棍中長度最大的那根max,原始長度len一定大於等於這個max,所以我們從max開始搜尋,若找到乙個符合條件的原始長度len就立刻返回,此時這個len一定是最小值;

考慮一下怎麼搜:我們設search(int k,int last,int rest) 表示當前正在拼接第k根木棍,上一根用到的小木棍是last,當前還有rest沒拼接完;

當然若這個題不加任何剪枝技巧進行深度優先搜尋的話,時間效率是指數級別的,效率非常低,程式將嚴重超時。對於此題我們可以從可行性和最優性上加以剪枝:

1.所以木棍的總長度為sum,那麼原始長度len一定能夠被sum整除,即len | sum ,因為你要拼出來整數根木棍,不可能拼出來小數根木棍;

2.木棍的原始長度一定大於等於所有小木棍中最長的那根;

3.一根長木棍肯定比幾根短木棍拼成同樣長度的用處小,即短的木棍可以更靈活組合,所以對輸入的所有木棍按長度從大到小排序,從長到短地將木棍拼入,這樣短木棍可以更加靈活,拼成原始長度len的成功率更高;

4.在截斷後的排好序的木棍中,當用第i根木棍拼接時,可以從i+1後的木棍開始搜。因為根據優化3,你總是先用長度更大的木棍,所以前i個木棍已經用過了;

5.當dfs返回拼接失敗,需要更換當前使用的木棍時,不要再用與當前木棍的長度相同的木棍,因為當前木棍用了不行,改成與它相同長度的木棍一樣不行。這裡我開了個陣列nxt[i]表示與第 i 根長度相同的所有木棍中最後一根,那麼nxt[i]的下一根就是和i長度不相同的木棍;這個預處理可以優化時間,不必在迴圈中慢慢往下找長度不相等的木棍;

6.我們拼接木棍的時候只找長度小於等於剩餘長度rest的木棍(長了接不上啊),所以我們可以二分查詢到第一根長度小於等於rest的木棍,那麼它後面的木棍都是小於rest的木棍;

7.當我們搜到了最後一根木棍的時候,我們直接返回,因為剩下的一定能拼成原式長度len;

證明:  所有木棍的長度總和為sum,當前列舉的原式長度為len,那麼能拼成m=sum/len 根木棍;若當前正在拼第m根木棍的話,那麼說明前面的(m-1)根木棍已經拼好了,用了len*(m-1)的長度,那麼剩下的長度就是:sum-len*(m-1)=sum-(len*m-len)=sum-sum+len=len,說明剩下的所有木棍的總和就是len,正好就是列舉的原式長度len,那麼我們就不用搜了,直接返回就好了;

8.用vis陣列標記每根木棍是否用過。另外在dfs回溯的時候別忘了去掉這些標記,這樣就不用每次dfs之前memset了(memset用多的話速度可慢了)!

9.我們其實只需列舉到sum/2就行了,如果還拼不成功的話,那麼答案只能是sum了;

10.還有乙個挺重要的剪枝,但是容易忽略:

當我們剩餘長度rest等於當前拼接的木棍的長度時,若拼接失敗了,那麼直接返回改用之前的木棍;

這一點很難想也很難理解,當時我也特別的懵啊,現在好歹明白了,現在就給你們解釋一波吧(解釋得不是很到位,可能你們也挺不懂qwq):

當前長度rest等於正在拼的這個小木棍的長度,所以是不是我們只要把這個小木棍接上去就又拼完了一根木棍?但是我們去拼其他的木棍的時候卻拼接失敗了,如果我們不返回的話,繼續換木棍往下拼,一定是要用幾根和為rest的小木棍一塊拼才能把那個rest的空補上,那麼你原來那個剩下的長度為rest的木棍就擱在那兒了,你想想,前面說過短木棍比長木棍更靈活更好拼,那麼你剛剛的操作就相當於,用了好幾根短木棍去換下來了一根長木棍,但是那些短木棍都拼不成功,你這一根長木棍不就更拼不成功了嗎?

有了這麼多剪枝,**跑起來就快多了,下面就是ac**啦:

#include#include

#include

#include

#include

using

namespace

std;

int read() //

讀入優化

while(ch>='

0'&&ch<='9'

)

return a*x;

}int cmp(int x,int y) //

將小木棍長度從大到小排序

int n,sum,minn,m,len,flag,a[70],vis[70],nxt[70

],x,cnt;

//a陣列是合法的每根小木棍的長度

//vis陣列是看看每根小木棍是否已經用過

//nxt[i]陣列是看看和第i根小木棍出長度相同的所有木棍中的最後一根

void search(int k,int last,int rest) //

正在拼第k根大木棍,上一根用的小木棍是last,還剩rest

//剪枝7,如果當前正在拼最後一根,那麼肯定能拼成,就直接返回吧

if(rest==0) //

拼完了第k根,換下一根

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

while(l//

剪枝6,二分找第一根小於等於rest的木棍

for(i=l;i<=cnt;i++) //

剪枝4,後面的小木棍都比rest小

}return;}

intmain()

sort(a+1,a+1+cnt,cmp); //

剪枝3,讓小木棍從大到小排序

nxt[cnt]=cnt; //

最後一根小木棍的nxt只能是自己,後面沒有小木棍了

for(int i=cnt-1;i>0;i--)

vis[

1]=1

;

for(int i=a[1];i<=sum/2;i++) //

剪枝9,列舉可能的每根長度 }}

if(!flag) cout<

return0;

}

好一道剪枝神搜題啊qwq!累死我了!

P1120 小木棍 資料加強版

喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過 50 現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。給出每段小木棍的長度,程式設計幫他找出原始木棍的最小可能長度。首先在讀入的時候忽略掉長度大於50的木棍 最重要 順便還要記錄一下最短的木...

P1120 小木棍 資料加強版

一道不錯的剪枝題,用到的剪枝優化比較多,剪枝思路也值得學習 在這裡我只採取了能通過此題的剪枝,似乎還可以繼續優化?剪枝講解在注釋裡 include include include include includeusing namespace std const int maxn 70 inline ...

P1120 小木棍 資料加強版

喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過 505050 現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。給出每段小木棍的長度,程式設計幫他找出原始木棍的最小可能長度。輸入格式 共二行。第一行為乙個單獨的整數n表示砍過以後的小木棍的總...