POJ1011小木棍題解
POJ1011小木棍題解
首先我認為題目中的喬治就是個傻X——哪有切斷又接回去的((
轉回正題。
題目傳送門:POJ1011
題意簡介
有個智力低下(盲猜此人乃近親通婚之產物)的人名叫喬治,他有一天突然得到了一組長度相同的木棍(物業不知道有多少個),然後他的大腦皮層肌肉死亡痙攣,突然決定將這些木棍隨機切碎,長度不一。好嘛,這個人的大腦皮層肌肉突然復活,又痙攣了一下,很快啊,很快,他就決定將這些木棍拼接回去。現在給你切碎後每個小木棍的長度,問你最開始那組長度一致的木棍,每根木棍的長度至少是多少。儘管你覺得他不講武德,但你還是要做這道題(耗子尾汁)。
題意分析
首先我們發現可以列舉答案\(len\),從小到大列舉,符合條件就退出。
如何判斷他是否滿足條件呢?我們可以使用搜索來解決這道題。搜尋中要維護\(3\)個狀態,變數\(stick\)表示當前正在搞第\(stick\)根原始木棍,\(cab\)表示當前正在湊的這根原始木棍的長度是多少(換句話說\(len-cab\)就是我們要成全這根木棍需要湊出的長度),\(lst\)表示上次湊時使用的小木棍。
然後你就發現你\(TLE\)了。
如果你是一個老\(OIer\),你肯定知道這道題是道剪枝模板題。怎麼剪枝呢?本題有以下\(5\)種剪枝。
\((1).\)優化搜尋順序:顯然先嚐試較長的小木棍比隨機嘗試要優,所以我們要在搜尋前先排序。
\((2).\)排除等效冗餘:限制先後加入一根切碎後的木棒的長度是遞減的,因為先加入\(x\)
\((3).\)排除等效冗餘:對於當前原始木棍,記錄其最近一次嘗試的新木棍,如果分支失敗回溯,那麼不必繼續嘗試向該原始木棍中新增其他等長的新木棍,因為也必定失敗。
\((4).\)排除等效冗餘:如果在當前原始木棍中新增一根新木棍並恰好拼接完整,並且接下來的兒子分支遞迴失敗,那麼直接判定當前分支失敗,原理類似於遠遠地望去是一個死衚衕就立刻回頭。
\((5).\)如果在當前原始木棍中嘗試接入的第一根木棍就失敗,那麼直接判定分支失敗並立即回溯。因為在這之前這根原始木棍是空的,後面的原始木棍必定也是空的,這跟空的接不了後面的肯定也接不了
AC程式碼:POJ1011 AC 16ms/640kb
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110;
int a[N],v[N],n,len,cnt;
int sum,val;
int read()
{
int x=0,f=1;
char c=getchar();
while (c<'0'||c>'9')
{
if(c=='-') f=-1;
c=getchar();
}
while (c>='0'&&c<='9')
{
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
bool DFS(int stick,int cab,int lst)
{
if(stick>cnt) return true;
if(len==cab) return DFS(stick+1,0,1);
int fail=0;
for(int i=lst;i<=n;i++)
{
if(!v[i] && cab+a[i]<=len && fail!=a[i])
{
v[i]=1;
if(DFS(stick,cab+a[i],i+1)) return true;
fail=a[i];
v[i]=0;
if(!cab || cab+a[i]==len) return false;
}
}
return false;
}
int main()
{
while (n=read())
{
if(!n) break;
sum=0,val=0;
for(int i=1;i<=n;i++)
{
a[i]=read();
sum+=a[i];
val=max(val,a[i]);
}
sort(a+1,a+n+1);
reverse(a+1,a+n+1);
for(len=val;len<=sum;len++)
{
if(sum%len) continue;
cnt=sum/len;
memset(v,0,sizeof(v));
if(DFS(1,0,1)) break;
}
cout<<len<<endl;
}
return 0;
}