洛谷P1120 小木棍 [数据加强版]

P1120 小木棍 [数据加强版]:

题目描述:

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

现在,他想把小木棍拼接成原来的样子,但是却忘记了自己开始时有多少根木棍和它们的长度。
给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。


输入格式:

共二行。

第一行为一个单独的整数N表示砍过以后的小木棍的总数,其中N≤65

(管理员注:要把超过50的长度自觉过滤掉,坑了很多人了!)

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


输出:

一个数,表示要求的原始木棍的最小可能长度


测试样例:

输入
9
5 2 1 5 2 1 5 2 1

输出
6

思路:

一道比较复杂的题目(主要难度在于对代码进行剪枝),先输入所有木棍的长度,从其中最大的开始搜索,判断以当前长度是否所有木棍能拼接成,如果可以就输出当前长度,退出循环。

AC代码:

#include <bits/stdc++.h>
using namespace std;
int n,a[70],c[70],m;
int sum=0,maxmum=0,len;
bool flag,b[70];//数组b用来表示该位置的小木棍有没有被使用组合原始木棍。
void dfs(int t,int last,int rest)
{
    if(rest==0)
    {
        if(t==m-1)//显然如果已经有m-1根满足了,最后一根必定满足,不需要继续组合,直接输出长度
        {
            cout<<len<<endl;
            exit(0);//退出程序
        }
        int k=n;
        for(int i=1; i<=n; i++)
        {
            if(!b[i]//下一次搜索时,从第一根没有被使用过的木棍开始
            {
                k=i;
                break;
            }
        }
        b[k]=1;//标记
        dfs(t+1,k+1,len-a[k]);//下一个情况
        b[k]=0;//回溯
        return;
    }
    int l=last,r=n,mid;
    while(l<r)//使用二分,找到第一根长度小于等于原始木棍长度的木棍
    {
        mid=(l+r)/2;
        if(a[mid]<=rest)r=mid;
        else l=mid+1;
    }
   /*如果当前长棍剩余的未拼长度等于当前木棍的长度或原始长度,继续拼下去时却失败了,就直接回溯之前拼的木棍。*/ 
    for(int i=l; i<=n; i++)
    {
        if(!b[i]&&a[i]<=rest)
        {
            b[i]=1;
            dfs(t,i+1,rest-a[i]);
            b[i]=0;
            if(rest==a[i]||rest==len)
            {
                return;
            }
            i=c[i];
            i--;
        }
    }
}
int main()
{
    int t,x;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        cin>>x;
        if(x>50)
        {
            continue;//按照题目要求,当小木棍的长度大于50时候,不储存到数组中
        }
        a[++n]=x;
        sum+=a[n];//统计所有成功输入的木棍的长度和
        if(maxmum<a[n])
        {
            maxmum=a[n];//得到所有木棍长度中最长的那根,作为原始木棍的最小长度(如果原始木棍的长度小于最长木棍的长度,那显然是不满足题意的)
        }
    }
    sort(a+1,a+n+1,greater<int>());//便于下一个优化
    for(int i=1; i<=n; i++)
    /*很重要的一个优化。在搜索的时候,如果某一长度被确定无法被使用,那么后续一些等长的木棍就不需要再进行尝试了,直接从下一个长度的木棍继续尝试。*/
    {
        int j=i,k=i;
        if(k<n&&a[j]==a[k])
        {
            while(k<n&&a[j]==a[k])
            {
                k++;
            }
            c[j]=k;
        }
        else
        {
            c[j]=k+1;
        }
    }
    
    for(len=maxmum;len<=sum/2;len++)
    {
        int temp=sum%len;//只有当原始木棍的长度能够被木棍总长度整除的时候,才有可能满足题意,否则不可能,应直接跳过进行下一个枚举
        if(temp!=0)
        {
            continue;
        }
        m=sum/len;//计算得到一共可以组成多少根原始木棍
        memset(b,false,sizeof(b));
        dfs(1,1,len);
    }
    if(!flag)
    /*如果上一步的搜索都没有得到正确的输出,那么就输出所有木棍的长度和,即所有木根合在一起拼接成一根木棍。*/
    {
        cout<<sum<<endl;
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值