題解 UVA624 【CD】
阿新 • • 發佈:2021-06-22
演算法分析:01揹包+記憶路徑
對於每一片 CD ,只有選和不選兩種情況。直接 dfs 最壞情況下時間複雜度達到 \(O(n^2)\) ,顯然不能過。由於只有兩種情況,考慮01揹包。用 \(dp_j\) 表示音軌長度為 \(j\) 時的最優解。容易得到:
\(dp_j=\max(dp_{j-a_i}+1,dp_j)(a_i \le j \le n)\)
初始化 \(dp_j=-1(1\le j \le n)\),邊界 \(dp_0=0\)。
那麼時間的總和不難求出。
接下來講一下本題的難點:記憶路徑。
如何記憶路徑呢?我們可以記錄當前的 \(dp_j\) 是從哪個狀態轉移過來的,由於最後的答案我們已經知道,那麼可以從答案往回推,得到選擇的音軌序列。
我們考慮使用二維陣列 \(rec(i,j)\) 記錄當前音軌長度為 \(i\),且從 \(j\) 推過來時選擇的音軌(可能有點繞,看程式碼就明白了)。當 \(dp_j\) 被 \(dp_{j-a_i}\) 更新時,同時更新 \(rec_{dp_j,j}=i\) 。求出答案後,對於所有的狀態掃描一遍,找到最大的合法狀態,然後回推,然後倒序輸出。
下面是喜聞樂見的程式碼:
#include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define reg register #define F(i,a,b) for(reg int i=(a);i<=(b);i++) using namespace std; const int N=1e4+5; int n,m,dp[N],rec[100][N],fa[N]; int a[N]; int main() { while(scanf("%d%d",&n,&m)!=EOF) { memset(rec,0,sizeof(rec)); memset(dp,-1,sizeof(dp));//初始化 reg int sum=0; F(i,1,m)scanf("%d",&a[i]); dp[0]=0;//邊界 F(i,1,m) { for(reg int j=n; j>=a[i]; j--) { if(dp[j-a[i]]==-1)continue;//不合法的狀態,不做了 if(dp[j]<dp[j-a[i]]+1) { dp[j]=dp[j-a[i]]+1; rec[dp[j]][j]=i;//記錄路徑(可以理解為一條 dp[j]->j 的反向邊) } } } reg int id=0,now=0,num; for(reg int i=n;i>=1;i--){ if(dp[i]!=-1){//找到最大的合法長度 num=id=dp[i],now=i; break; } } while(id) { reg int &pos=a[rec[id][now]]; fa[id]=pos;//記錄路徑 sum+=pos;//記錄長度 now-=pos,id--;//沿著反向邊回推 } F(i,1,num)printf("%d ",fa[i]);//倒序輸出 printf("sum:%d\n",sum);//注意輸出格式 } return 0; }
(目前最優解)
歡迎交流討論,請點個贊哦~