题意

对于n个硬币堆,如果想选取一些硬币堆,一堆一堆地叠起来,将它们叠到正好H个硬币那样高(不可以随意增加或减少每个硬币堆的硬币数量),最少需要几个硬币堆。可以堆成则输出方案,不行输出-1.

思路

如果只是单纯的求需要几个硬币堆,很容易写DP,但是这个题还要把方案也一并输出。我们可以额外的再开一个数组记录,记录的是构成高度为j但除了当前这一堆硬币,其他硬币的总高度。求方案的时候在用 j-f[i][j] 求到的就是选择当前这堆硬币,硬币的高度。

递推方程也很容易写:

dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]+1);

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e6 + 7;
const int mod = 1e9 + 7;
const int MOD = 998244353;
#define sc(x) scanf("%lld", &(x))
#define pr(x) printf("%lld\n", (x))
#define int long long
#define rep(i, l, r) for (int i = l; i <= r; ++i)
int a[N],n,H;

int dp[3010][3010];//这个记录答案
int f[3010][3010];//记录的是除了当前这一堆硬币,其他硬币的高度
//dp[i][j]表示从前i个选高度正好为H的最少堆数
//dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]+1);
void solve(){
	cin>>n>>H;
	for(int i=1;i<=n;i++){
        cin>>a[i];
    }sort(a+1,a+n+1);
    for(int i=0;i<=n;i++){
        for(int j=0;j<=3000;j++){
            dp[i][j]=-1;
        }
    }
    dp[0][0]=0;
    for(int i=1;i<=n;i++){
        dp[i][a[i]]=1;
        dp[i][0]=0;
        for(int j=1;j<=H;j++){
            dp[i][j]=dp[i-1][j];
            f[i][j]=j;
        }
        for(int j=a[i];j<=H;j++){
            if(dp[i-1][j-a[i]]>=0&&(dp[i][j]==-1||dp[i][j]>=dp[i-1][j-a[i]]+1)){
                dp[i][j]=dp[i-1][j-a[i]]+1;
                f[i][j]=j-a[i];
            }
        }
    }
    if(dp[n][H]==-1){
        cout<<-1<<endl;
        return;
    }
    vector<int> ans;
    for(int i=n,j=H;i>=1;i--){
        if(j-f[i][j]) ans.push_back(j-f[i][j]);
        j=f[i][j];
    }
    reverse(ans.begin(), ans.end());
    cout<<ans.size()<<endl;
    for(auto&t:ans){
        cout<<t<<" ";
    }
    cout<<endl;
}

signed main(){
	int _=1;
	//cin>>_;
	while(_--) solve();
	return 0;
}