1. 程式人生 > 其它 >題解 UVA624 【CD】

題解 UVA624 【CD】

題目傳送門

演算法分析: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;
}

AC

(目前最優解)

歡迎交流討論,請點個贊哦~