题解:CF687C The Values You Can Make

CF687C The Values You Can Make 题解

题目翻译感觉不明不白的(至少我看了几遍没看懂),这里给个较为清晰的题面。

题目描述

给你 \(n\) 个硬币,第 \(i\) 个硬币有一个价值 \(c_i\),你需要从中选出一些价值和为 \(k\) 的硬币组成一个集合,再输出这个集合中硬币可能组成的价值和。

算法

动态规划,背包。

分析

看完我给的题面描述其实已经很清晰了。这道题可以分为两步:

  • \(n\) 个硬币中选出来价值和为 \(k\) 的硬币集合。
  • 输出硬币集合所能凑出的价值和。

我们很容易发现如果是任意一个步骤都很好做,用 \(01\) 背包解决。看数据范围 \(n\le 500\),考虑套两个 \(01\) 背包。

现在设计状态。我们发现对于每个硬币,它都有三种处理可能:不选入硬币集合,选入硬币集合但不用来凑价值和,选入硬币集合且用来凑价值和。所以我们设 \(dp[i][j][k]\) 表示前 \(i\) 个硬币中选出价值和为 \(j\) 的硬币集合,用来凑出价值和 \(k\) 是否可行。

状态转移方程就很好推了,对于前一个硬币有上述三种处理可能,只要有一种可行那么这个硬币就可行,转移方程为 \(dp_{i,j,k}=dp_{i-1,j,k}|dp_{i-1,j-c_i,k}|dp_{i-1,j-c_i,k-c_i}\)。分别对应三种处理可能。

注意一下边界 \(dp_{0,0,0}=1\),最终遍历一遍 \(dp_{n,k,p}(0\le p\le 500)\),如果为真就说明可以凑出输出即可,复杂度 \(\mathcal O(n^3)\)

注意一下直接开三维数组会 MLE,需要用滚动数组优化,这里不细说了,和 \(01\) 背包一样。

代码

码风较丑,不喜勿喷。(@jiayixuan1205 的好看去看她的

#include<bits/stdc++.h>
using namespace std;
namespace Ryan
{
    const int N=500,M=505;
    int dp[M][M],c[M];
    int n,kk;
    signed work()
    {
        cin>>n>>kk;
        for(int i=1;i<=n;i++)
            cin>>c[i];
        dp[0][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=N;j>=0;j--)
                for (int k=N;k>=0;k--)
                    if(j>=c[i])
                    {
                        dp[j][k]|=dp[j-c[i]][k];
                        if(k>=c[i])
                            dp[j][k]|=dp[j-c[i]][k-c[i]];
                    }
        int ans=0;
        for(int i=0;i<=N;i++)
            if(dp[kk][i])ans++;
        cout<<ans<<endl;
        for(int i=0;i<=N;i++)
            if(dp[kk][i])
                cout<<i<<" ";
        return 0;
    }
}
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    return Ryan::work();
}
posted @ 2024-08-01 13:30  Ryan_Adam  阅读(5)  评论(0编辑  收藏  举报