【解題報告】洛谷P1120 小木棍
【解題報告】洛谷P1120 小木棍
題目連結
https://www.luogu.com.cn/problem/P1120
思路
——摘自《演算法競賽進階指南》
我們可以從小到大列舉原始木棒的長度 \(len\) , 它應該是所有木棍長度的和 \(sum\) 的因數,並且原始木棒的根數 \(cnt\) 應該等於 \(\dfrac {sum} {len}\)
對於每一個列舉的 \(len\) ,我們可以一次搜尋每根原始木棒由哪些木棍拼成,具體的講,也就是搜尋的狀態是 :已經拼好的原始木棒的數量,正在拼的原始木棒的當前長度,每個木棍的使用情況,在麼一個狀態下,我們從還沒有用的木棍中選擇一個,嘗試拼到當前的原始木棒中,然後遞迴到新的狀態,遞迴邊界就是成功拼好 \(cnt\)
這個演算法的效率比較低,我們可以進行幾類剪枝:
- 優化搜尋順序
把木棒從大到小排序,優先嚐試比較長的木棍
- 排除等效冗餘
可以限制先後加入一根原始木棒的木棍長度是遞減的,這是因為先拼上一根長度為 \(x\) 的木棍,再拼上一根長度為 \(y\) 的木棍,和先拼 \(y\) 再拼 \(x\) 一毛一樣,所以只用搜尋其中一種
對於當前的原始木棒,記錄最近一次嘗試拼接的木棒的長度,如果分支搜尋失敗回溯,就不再嘗試向該木棒中拼接其他相同的木棒,因為必定也會失敗
如果當前原始木棒中,嘗試拼入的第一根木棍的遞迴分支就失敗了,那這個分支肯定失敗,立馬回溯,這是因為在拼入這根木棒之前,面對的所有的木棍都是空的,這些木棒都是等效的,木棍拼在當前的木棒裡面失敗,拼在其他的木棒裡面也一定會失敗
如果在當前的原始木棒裡面拼入一根木棍之後,木棒恰好被拼接完整,並且接下來拼接剩餘原始木棒的遞迴分支返回失敗,那麼就直接判定當前的分支失敗,立即回溯,該剪枝可以使用貪心來解釋,再用1根木棍敲好拼完當前的原始木棒必然比再用若干根木棍拼完當前的木棒更加好
這樣運用一些等效性和貪心就剪掉了搜尋樹上面的很多分支,提高了搜尋的效率
本博文為wweiyi原創,若想轉載請聯絡作者,qq:2844938982#include <iostream> #include <cstdio> #include <algorithm> #include <cstring> #include <string> using namespace std; const int maxn=3300; int n,ans,res,tot,mx,sum; int s,l; int a[70]; bool vis[maxn]; bool dfs(int now,int len,int cnt) { if(now>s) return true; if(len==l) return dfs(now+1,0,1); int f=0; for(int i=cnt;i<=tot;i++) { if(!vis[i]&&len+a[i]<=l&&f!=a[i]) { vis[i]=true; if(dfs(now,len+a[i],i+1)) return true; f=a[i]; vis[i]=false; if(len==0||len+a[i]==l) return false; } } return false; } int main() { std::ios::sync_with_stdio(false); cin.tie(0),cout.tie(0); cin>>n; for(int i=1;i<=n;i++) { int x; cin>>x; if(x>50) continue; a[++tot]=x; mx=max(mx,x); sum+=x; } sort(a+1,a+tot+1); reverse(a+1,a+tot+1); for(l=mx;l<=sum;l++) { if(sum%l) continue; s=sum/l; memset(vis,false,sizeof(vis)); if(dfs(1,0,1)) break; } cout<<l<<'\n'; return 0; }