1. 程式人生 > >洛谷P1120 小木棍 [資料加強版]【搜尋+毒瘤剪枝】

洛谷P1120 小木棍 [資料加強版]【搜尋+毒瘤剪枝】

時空限制 300ms-1000ms / 128MB

題目描述

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

輸入格式:

共二行。 第一行為一個單獨的整數N表示砍過以後的小木棍的總數,其中N≤65 (管理員注:要把超過50的長度自覺過濾掉,坑了很多人了!) 第二行為N個用空個隔開的正整數,表示N根小木棍的長度。

輸出格式:

一個數,表示要求的原始木棍的最小可能長度

說明

-#17 #20 #22 #27 四組資料時限500ms -#21 #24 #28 #29 #30五組資料時限1000ms 其他時限改為200ms

題目分析

練習搜尋剪枝的絕世好(du)題(liu) 首先確定搜尋思路在逐步加入剪枝

我們可以列舉原來每段木棍的長度len來搜尋判斷是否可行 先計算出木棍總長sumsum,顯然lenlen一定是sumsum的約數 確定了lenlen的同時,我們也確定了原來木棍個數cnt=sum/lencnt=sum/len

for(len=mx;len<=sum;++len)//mx時切割後所有小木棍中最長的那個的長度
{
    if(sum%len) continue;
    cnt=sum/len;
    memset(vis,0,sizeof(vis));
    if
(dfs(1,0)) break; }

首先最簡單最暴力的搜尋,可得27pts

int dfs(int k,int m)//當前以拼完k-1個木棍,正在拼第k個,且第k個木棍當前長m
{
    if(k==cnt+1) return 1;//拼完所有cnt個,方案合法
    if(m==len) return dfs(k+1,0);//拼完了第k個,開始拼下一個
    for(int i=1;i<=n;++i)
    {
        if(vis[i]||m+a[i]>len) continue;
        vis[i]=1;//嘗試將第i個小木棍拼上去
        if(dfs
(k,m+a[i])) return 1; vis[i]=0; //回溯後記得重置 } return 0; }

現在開始考慮剪枝 1.一個最明顯的剪枝就是一開始小木棍從大到小排序 這一點比較簡單不解釋了,排序後33pts

2.若當前木棍還沒拼完,那麼繼續搜尋拼接這個木棍的小木棍時 前面已列舉過的顯然不用再考慮

剪枝後39pts

int dfs(int k,int m,int last)//last記錄上一次列舉到的位置
{
    if(k==cnt+1) return 1;
    if(m==len) return dfs(k+1,0,1);
    for(int i=last;i<=n;++i)//從last開始列舉
    {
        if(vis[i]||m+a[i]>len) continue;
        vis[i]=1;
        if(dfs(k,m+a[i],i+1)) return 1;
        vis[i]=0; 
    }
    return 0;
}

3.假如已嘗試將某個長度為aia_i的小木棍拼接到當前木棍不可行 那麼相同長度的木棍不用再搜尋

剪枝後54pts

int dfs(int k,int m,int last)
{
    if(k==cnt+1) return 1;
    if(m==len) return dfs(k+1,0,1);
    int pre=0;//記錄上一次用的長度
    for(int i=last;i<=n;++i)
    {
        if(vis[i]||m+a[i]>len||a[i]==pre) continue;//計入a[i]==pre,相同則跳過的剪枝
        vis[i]=1;
        if(dfs(k,m+a[i],i+1)) return 1;
        vis[i]=0; pre=a[i]; //到達這裡說明ai不可行,用pre記錄
    }
    return 0;
}

4.假設當前正在拼接的木棍長度為0第一次嘗試將某個小木棍aia_i拼接不可行後,直接回溯

因為若當前小木棍長度為0,說明剩餘沒拼的木棍都是等價的(都是0) 若把aia_i放入當前木棍不可行,那麼放入其他等價的木棍顯然也不可行

剪枝後78pts

int dfs(int k,int m,int last)
{
    if(k==cnt+1) return 1;
    if(m==len) return dfs(k+1,0,1);
    int pre=0;
    for(int i=last;i<=n;++i)
    {
        if(vis[i]||m+a[i]>len||a[i]==pre) continue;
        vis[i]=1;
        if(dfs(k,m+a[i],i+1)) return 1;
        vis[i]=0; pre=a[i]; 
        if(m==0) return 0;//若第一次搜尋就不成功,直接回溯
    }
    return 0;
}

5.若當前正在拼接的木棍長度+當前列舉的小木棍長度aia_i==lenlen 且此次向下搜尋判定為不合法,則可以直接回溯

因為如果繼續列舉,用兩個更小的木棍湊出aia_i,顯然與上述也是等價的

剪枝後100pts

int dfs(int k,int m,int last)
{
    if(k==cnt+1) return 1;
    if(m==len) return dfs(k+1,0,1);
    int pre=0;
    for(int i=last;i<=n;++i)
    {
        if(vis[i]||m+a[i]>len||a[i]==pre) continue;
        vis[i]=1;
        if(dfs(k,m+a[i],i+1)) return 1;
        vis[i]=0; pre=a[i]; 
        if(m==0||m+a[i]==len) return 0;
    }
    return 0;
}

完整程式碼

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int maxn=100;
int n,a[maxn],ans;
int vis[maxn],stick[maxn];
int sum,mx,cnt,len;
bool cmp(int a,int b){return a>b;}

int dfs(int k,int m,int last)
{
    if(k==cnt+1) return 1;
    if(m==len) return dfs(k+1,0,1);
    int pre=0;
    for(int i=last;i<=n;++i)
    {
        if(vis[i]||m+a[i]>len||a[i]==pre) continue;
        vis[i]=1;
        if(dfs(k,m+a[i],i+1)) return 1;
        vis[i]=0; pre=a[i]; 
        if(m==0||m+a[i]==len) return 0;
    }
    return 0;
}

int main()
{
    n=read();
    for(int i=1;i<=n;++i)
    {
    	int x=read();
    	if(x>50) continue;
    	a[++cnt]=x;
    	mx=max(mx,a[cnt]); sum+=a[cnt];
    }
    
    n=cnt; cnt=0; sort(a+1,a+1+n,cmp);
    for(len=mx;len<=sum;++len)
    {
        if(sum%len) continue;
        cnt=sum/len;
        memset(vis,0,sizeof(vis));
        if(dfs(1,0,1)) break;
    }
    printf("%d",len);
    return 0;
}