题解 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;
}
(目前最优解)
欢迎交流讨论,请点个赞哦~
本文来自博客园,作者:Maplisky,转载请注明原文链接:https://www.cnblogs.com/lbh2021/p/14920545.html