[USACO05DEC]Scales S题解

提供一个可能比较麻烦(但是思路可能也有一定价值)的解法

搜索+局部记忆化(好吧其实就是搜索+$DP$



搜索

首先$n<=1000$这一看就不是搜索能做的啊,然而观察到数据具有如下性质

每个砝码的质量至少等于前面两个砝码(也就是质量比它小的砝码中质量最大的两个)的质量的和


考虑极限数据应为斐波那契数列,$n$最大可达到$45$左右,普通爆搜的复杂度为$O(2^n)$,虽然还是不能通过的,但是至少极大减小了搜索的状态量

$DP$

这个问题也是一个经典的$DP$模型啊,设$dp[i]$为容量为$i$时所能放砝码的最大重量

容易发现以砝码重量同时作为物品的重量和价值,本题就可以转化为标准的$01$背包,然而$C<=2^{30}$的状态量显然是过大的

不妨结合以上两种算法

重量较小的元素状态量少,可以进行$DP$;重量较大的元素数量较少,可以进行搜索。具体而言,首先进行预处理,仅取重量较小的元素进行$DP$,$dp[i]$表示存储容量为$i$的背包能够装下砝码的最大重量,然后对于剩下的重量较大的元素进行搜索,对于每种搜索方案,可将容纳量与该方案的已取总重量之差作为新的总容纳量,利用预处理的数据$O(1)$求得该搜索方案所能达到的最优解

复杂度分析

记忆化($DP$)对复杂度的优化在于避免重复搜索,本题局部记忆化的价值就在于通过一次预处理求出重量较小的砝码的集合在不同容量下的最优选取方案,在搜索时只需枚举数量较少的剩下的元素的选取情况,从而大大减少了搜索过程的时间复杂度

下面以极限数据斐波那契数列为例具体分析本算法的时间复杂度,可以证明对于其他符合条件的数据,算法的理论时间复杂度不大于极限数据

经计算可得斐波那契数列在约$45$项达到$C$的数据上限,取$dp$的状态上限为$2*10^5$,约为斐波那契数列的前$25$项和

$dp$的复杂度约为$5*10^6$,搜索的复杂度约为$10^6$,总时间复杂度约为$6*10^6$,明显可过

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=2e5;
int a[1010],n,c,ans,sum[1010],cnt,dp[maxn+10];
void search(int p,int s)
{
    if(s>c)
        return;
    if(p==cnt+1)
    {
        if(c-s>maxn)
            ans=max(ans,s+sum[n]-sum[cnt]);
        else
            ans=max(ans,s+dp[c-s]);
        return;
    }
    search(p+1,s+a[p]),search(p+1,s);
}
signed main()
{
    scanf("%lld%lld",&n,&c);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        if(a[i]>c)
        {
            n=i-1;
            break;
        }
    }
    for(int i=1;2*i<n;i++)
        swap(a[i],a[n-i+1]);
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+a[i];
    for(int i=n;i;i--)
        if(sum[n]-sum[i]>maxn)
        {
            cnt=i+1;
            break;
        }
    for(int i=cnt+1;i<=n;i++)
        for(int j=maxn;j>=a[i];j--)
            dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
    for(int i=1;i<=maxn;i++)
        dp[i]=max(dp[i],dp[i-1]);
    search(1,0);
    printf("%lld\n",ans);
    return 0;
}

 

posted @ 2020-11-23 20:08  Ivanovcraft  阅读(136)  评论(0编辑  收藏  举报