洛谷 P1120 小木棍 题解

这就是一道明显的爆搜题(人家洛谷也说了,,,),只是需要几个小小的优化,接下来为大家介绍一下:

1、将木棍先排个序(用处后面会讲),记住 长度>50 的要去掉

2、因为每一根的长度都不大于50,所以可以用桶装,既省空间也省时间

3、……先上代码吧,要不然直接讲比较抽象(其实是我口才不好)……

 

————————————(华丽的分割线)————————————————

#include <cstdio>
#include <cstdlib>
#include <cstring>


int n=0,m;
int a[110];//桶
int p=0;//木棍总长度
int max=0,min=51;//最长的木棍的长度以及最短的木棍的长度
int ans;//记录当前成功拼接了几根木棍,在dfs中用到


void dfs(int x,int y,int z)//x表示每根木棍的期望长度,y表示现在拼成的长度,z表示上一次使用的木棍的长度 
{
    if(ans*x==p)//假如拼接成功 
    {
        printf("%d",x);
        exit(0);//直接结束,此时一定是最优解,因为他要的是最小的解
   //而我们是从小到大枚举的,第一个解必然是最优解 
    }
    for(int i=z;i>=min;i--)//因为较长的木棍更难拼,所以从长的先开始
    {
        if(a[i]&&y+i<=x)//假如还有长度为i的木棍以及拼接起来不会超过期望长度的话
        {
            y+=i;
            if(y==x)ans++,y=0;
            a[i]--;
            if(y==0)dfs(x,y,max);//如果拼接成功,则z要从max开始
            else dfs(x,y,i);
            a[i]++;
            if(ans>0&&y==0)y=x-i,ans--;
            else y-=i;
            if(y==0)break;//最重要的优化1:见下文
            if(y+i==x)break;//最重要的优化2:同上
        }
    }
}


int main()
{
    scanf("%d",&m);
    memset(a,0,sizeof(a));//一定要记得初始化!!!
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        if(x<=50)//过滤掉超过50的
        {
            a[x]++,p+=x;
            max=max<x?x:max;
            min=min>x?x:min;
        }
    }
    for(int i=max;i<=p/2;i++)//枚举原本木棍的长度,用dfs尝试是否能拼接成功 
//由于原本木棍的长度不可能比最长的木棍长度要短,所以从max开始 
//以及原本木棍的长度一定能整除总长度(商为1的情况下面会处理),所以假如i>p/2的时候
//i一定不能整除p,所以没必要dfs,直接跳过 
    {
        if(p%i==0)//如果不能整除则跳过(不能整除则说明会有余数,也就是多余的木棍) 
        {
            ans=0;//一定要记得初始化!!! 
            dfs(i,0,max); 
        }
    }
    printf("%d",p);//假如上面未成功,就只能把全部拼在一起了
}

最重要的优化1:首先要思考,什么时候y==0,当然是刚开始拼接新的一根木棍时,而这条语句在dfs下面,也就是说拼接没有成功,才会到达这里,也就剩下那么些木棍,如果既然拼接都不成功了,那么也就没有必要尝试其他的了,于是break

优化2:上面y==0 break之后,就会到达上一根木棍的最后拼接的时候,那前面都知道是不可以的了,那也没有必要继续了,于是也break

以上就是我的想法,如果有人有更好的优化的话,欢迎评论

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值