暴搜——小木条加强版

题目描述

乔治有一些同样长的小木棍,他把这些木棍随意砍成几段,直到每段的长都不超过5050。

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

输入输出格式

输入格式:
共二行。

第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65N≤65
(管理员注:要把超过5050的长度自觉过滤掉,坑了很多人了!)

第二行为NN个用空个隔开的正整数,表示NN根小木棍的长度。

输出格式:
一个数,表示要求的原始木棍的最小可能长度
1.一根长木棍肯定比几根短木棍拼成同样长度的用处小,即短的木棍可以更灵活组合,所以对输入的所有木棍按长度从大到小排序,从长到短地将木棍拼入,这样短木棍可以更加灵活地接在。

 如果你还不太清楚“灵活”的含义,请形象脑补一下——如果先用短木棍,那么需要很多根连续的短木棍接上一根长木棍才能拼成一根原来的长棍,那么短木棍都用了后,剩下了大量长木棍,拼起来就不如短木棍灵活,更难接出原始长度。而先用长木棍,最后再用短木棍补刀,这样就剩下了相对较短的木棍,能更加灵活地拼接出原始长度。

2.根据优化1,将输入的木棍从大到小排好序后,当用木棍i拼合原始长棍时,从第i+1根木棍开始往后搜。

3.当dfs返回拼接失败,需要更换当前使用的木棍时,不要再用于当前木棍的长度相同的木棍,因为当前木棍用了不行,改成与它相同长度的木棍一样不行。这里我预处理出了排序后每根木棍后面的最后一根与这根木棍长度相等的木棍(程序中的next数组),它的下一根木棍就是第一根长度不相等的木棍了。

 这个预处理可以优化时间,不必在循环中慢慢往下找长度不相等的木棍。

4.只找木棍长度不大于未拼长度rest的所有木棍。我看其他大部分人的做法(包括书上的啊)都是直接在循环中判断,但我认为可以根据木棍长度的单调性来二分找出第一个木棍长度不大于未拼长度rest。它后面的木棍一定都满足这个条件。

5.用vis数组标记每根木棍是否用过。另外在dfs回溯的时候别忘了去掉这些标记,这样就不用每次dfs之前memset了(memset用多的话速度可TM慢了)!

inline int read(){
    int x=0; bool f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
    for(; isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
    if(f) return x;
    return 0-x;
}
int n,m,a[66],next[66],cnt,sum,len;
bool used[66],ok; //used数组即优化5的vis数组,记录每根木棍是否用过;ok记录是否已找到答案。 
bool cmp(int a,int b){return a>b;}
void dfs(int k,int last,int rest){ //k为正在拼的木棍的编号,last为正在拼的木棍的前一节编号,rest为该木棍还未拼的长度
    int i;
    if(!rest){ //未拼的长度为0,说明这根原始长棍拼完了,准备拼下一个 
        if(k==m){ok=1; return;} //优化6,全部拼完并符合要求,找到答案,直接返回 

        for(i=1;i<=cnt;i++) //找一个还没用的最长的木棍打头即可。反正要想全都拼接成功,每根木棍都得用上 
            if(!used[i]) break;
        used[i]=1; 
        dfs(k+1,i,len-a[i]);
        used[i]=0;
        if(ok) return; //优化6,找到答案就退出 
    }
    //优化4,二分找第一个 木棍长度不大于未拼长度rest 的位置 
    int l=last+1, r=cnt, mid;
    while(l<r){
        mid=(l+r)>>1;
        if(a[mid]<=rest) r=mid;
        else l=mid+1;
    }
    for(i=l;i<=cnt;i++){
        if(!used[i]){ //优化5,判断木棍是否用过 
            used[i]=1;
            dfs(k,i,rest-a[i]);
            used[i]=0;
            if(ok) return; //优化6,找到答案就退出 

            if(rest==a[i] || rest==len) return; //优化7 
            i=next[i]; //优化3 
            if(i==cnt) return;
        }
    }
    //到了这里,说明这时候拼不成当前这根原始木棍了,传回失败信息并修改之前拼的木棍 
}
int main(){
    n=read();
    int d;
    for(int i=1;i<=n;i++){
        d=read();
        if(d>50) continue;
        a[++cnt]=d;
        sum+=d;
    }
    sort(a+1,a+cnt+1,cmp); //优化1,木棍按长度从大到小排序 
    //优化3,预处理next数组 
    next[cnt]=cnt;
    for(int i=cnt-1;i>0;i--){
        if(a[i]==a[i+1]) next[i]=next[i+1];
        else next[i]=i;
    }
    for(len=a[1];len<=sum/2;len++){ //枚举原始长度 
        if(sum%len!=0) continue; //如果不能拼出整数根 就跳过 
        m=sum/len; //优化6中的那个计算 
        ok=0;
        used[1]=1;
        dfs(1,1,len-a[1]);
        used[1]=0;
        if(ok){printf("%d\n",len); return 0;} //优化6,输出答案,退 
    }
    printf("%d\n",sum); return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值