1. 程式人生 > 實用技巧 >[USACO05DEC]Scales S題解

[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; }