题解 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\) 。求出答案后,对于所有的状态扫描一遍,找到最大的合法状态,然后回推,然后倒序输出。

code:

#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

(目前最优解)

欢迎交流讨论,请点个赞哦~

posted @ 2021-06-22 22:21  Maplisky  阅读(38)  评论(0编辑  收藏  举报