洛谷 P5194 Scales S

P5194

一 题目理解 :

​ 题目地址:

​ [P5194 USACO05DEC] Scales S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

​ 题目可以简单描述为,我们有n ( n ≤ 1000 ) (n\leq1000) (n1000)个砝码,我们可以选择其中任意x( 0 ≤ x ≤ n 0\leq x\leq n 0xn)个砝码,让这些砝码的总和在不超过一个上界限c ( c ≤ 2 30 ) (c\leq2^{30}) (c230)的情况下,找出一个最大值。然后,题目给出了一个条件,也就是从第三个砝码开始,每个砝码至少为前两个砝码的和。

​ 这个题目,第一眼,就是一个简单的0-1背包,但是,我们看看背包重量要到 2 30 2^{30} 230,就算我们使用一维的0-1背包,空间复杂度要到1e9的级别,如果说空间复杂度还可以接受,那么时间复杂度 O ( n ∗ m ) O(n*m) O(nm)大约要到 1 e 12 1e^{12} 1e12,很明显会TLE。

​ 但是题目,给出了一个重要的条件,也就是从第三个砝码开始,每个砝码至少为前两个砝码的和,也就是斐波那契数列,这样的话,在大约40项左右(如果存在第40项的话),我们的砝码值应该就要大于我们的c了,那么其实这个题我们用到的砝码最多是40个左右,考虑dfs。

二 解题方法

​ 对于这个题目,我们可以使用三个剪枝策略

1.倒序dfs

​ 因为,只要我们所选的砝码的质量大于我们的c,我们就要return,所以我们可以从大数往小数遍历,这样可以大大减少搜索的数目。

​ 对于这个问题,我们可以这么理解,我们都知道,dfs其实就是一棵搜索树,以这个题目为例,其实就是一颗二叉树,对于这棵二叉树,某一个节点可能有两个孩子节点,要么选则第x( 0 ≤ x ≤ n 0\leq x\leq n 0xn)个砝码,要么不选,当某个节点不满足条件(总质量大于c或者已经搜索完毕),那么,他就没有孩子,而一颗二叉树的深度越大,时间复杂度和空间复杂度越高,所以,我们剪枝要做的就是让二叉树的深度尽可能低,也就是一些不必要搜索的节点我们就不去搜索了。

​ 对于这个题目,强行解释好吧放弃强行解释,我们可以自己造一组数据看一下,比如,对于以下数据:

4 15
1 10 20 30

​ 我们可以画出他们的搜索树的图辅助我们去理解。 可能画的有些抽象QAQ,但是主打一个理解

​ 正序树如图1所示:

在这里插入图片描述


图一 正序列树图解

反序列树如图2所示:

在这里插入图片描述

图二 反序树图解

2.缩小n

​ 如前文所说,我们的n不可能到1000,那么我们就可以让我们递归的起点减少,我们可以将递归的起点变成第一个比c小的数字,这样也会减少一些递归的时间,但是,我个人感觉,这样剪枝产生的效果只是锦上添花,不会让原本TLE的不TLE,因为,就算不使用这一步,在倒序dfs中,那些比c大的砝码递归也是直接进入不选这个砝码的叶子节点,时间开销花在了递归上。但是,小技巧加上比较好。

3.利用前缀和

​ 我们还可以利用前缀和进行剪枝,如果到了某个节点,他前面所有砝码都选上也不会超过c,那么我们就不需要继续往下搜索了,这样也可以减小一定的复杂度。

​ 总结一下,这个题目的一个解题方法为dfs+剪枝技巧,但是我感觉对于剪枝帮助最大的就是1,而且我们以后做题也很可能会用到。

​ 综上,该题目的代码如下:

#P5194代码
#include<iostream>
using namespace std;
const int N=1e3+100;
typedef long long LL;
// 斐波那契额数列到 30左右就会超过int
LL f[N];
// LL dp[N][N];
LL n,c;
LL ans;
int flag=0;
LL pret[N];
#前缀和初始化
void init()
{
    for(int i=1;i<=n;i++){
        pret[i]=pret[i-1]+f[i];
    }
}
#求解某一段的前缀和
LL check(int l,int r)
{

    return pret[r]-pret[l-1];

}

void dfs(LL x,LL count){
    if(x==0||count>c)
    {
        return ;
    }  
    else 
    {
        ans=max(ans,count);
        if(count+check(1,x-1)<=c)
        {
            ans=max(ans,count+check(1,x-1));
            // cout<<"lllllll"<<" "<<"cccc"<<x<<check(1,x-1)<<endl;
            return ;
        }
        dfs(x-1,f[x-1]+count);
        dfs(x-1,count);

    }




}
int main()
{
    cin>>n>>c;
   

    int temp=n;
    for(int i=1;i<=n;i++)
    {
        cin>>f[i];
        if(f[i]>c)
        {
            temp=i-1;
        }

        
    }
    n=temp;
    init();
        
          for(int i=n;i>=1;i--){
            if(f[i]<c)
            {
                dfs(i+1,0);
                break;
            }
            else if(f[i]==c) 
            {
                cout<<c<<endl;
                return 0;

            }
          }
    cout<<ans<<endl;
    return 0;
}

三总结

  • 首先,学会了一种剪枝方式,反向dfs 当在一个题目当中,dfs的结束条件有一个上界的时候,我们可以从大往小dfs,这样可以大大减少搜索树的复杂度。
  • 第二,就是,注意对题目当中的条件敏感,比如这个题目,从第三个砝码开始,每个砝码至少为前两个砝码的和,斐波那契数列,我一开始就没有分析出来,我还在想1000 dfs不得搜到明年,综上,还是刷题太少了,算法学习不刷题纯小丑哈哈。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值