原題鏈結
不得不說,這題真的是一道深度搜尋毒瘤題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好一道剪枝神搜題啊qwq!累死我了!#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;
}
P1120 小木棍 資料加強版
喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過 50 現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。給出每段小木棍的長度,程式設計幫他找出原始木棍的最小可能長度。首先在讀入的時候忽略掉長度大於50的木棍 最重要 順便還要記錄一下最短的木...
P1120 小木棍 資料加強版
一道不錯的剪枝題,用到的剪枝優化比較多,剪枝思路也值得學習 在這裡我只採取了能通過此題的剪枝,似乎還可以繼續優化?剪枝講解在注釋裡 include include include include includeusing namespace std const int maxn 70 inline ...
P1120 小木棍 資料加強版
喬治有一些同樣長的小木棍,他把這些木棍隨意砍成幾段,直到每段的長都不超過 505050 現在,他想把小木棍拼接成原來的樣子,但是卻忘記了自己開始時有多少根木棍和它們的長度。給出每段小木棍的長度,程式設計幫他找出原始木棍的最小可能長度。輸入格式 共二行。第一行為乙個單獨的整數n表示砍過以後的小木棍的總...