问题描述:
有n种硬币,面值分别为v1,v2,v3...vn,每种硬币有无限多,给定非负整数s,可以选用多少个硬币,使得面值之和恰好为s?输出硬币数目的最小值和最大值,并且输出各自的选取方案(如果有多种方案,则输出硬币编号字典序较小的方案,输出每种选取方案的面值)。
分析:本质上市一个DAG上的路径问题,我们把每种面值看做一个点,表示还需凑足的面值,则初始状态为0,目标状态为0,若当前在i,则每使用一枚硬币j,状态转移到i-vj。
#include <bits/stdc++.h>
#include <string>
using namespace std;
const int MAXN = 10000;
const int INF = 0x3f3f3f3f;
int n, S, V[MAXN], d[MAXN], vis[MAXN];
int dpmax(int S) {
if(vis[S]) return d[S];
vis[S] = 1;
int &ans = d[S];
ans = -INF;
for(int i = 1; i <= n; ++i) {
if(S >= V[i]) ans = max(ans, dpmax(S - V[i]) + 1);
}
return ans;
}
int dpmin(int S) {
if(vis[S]) return d[S];
vis[S] = 1;
int &ans = d[S];
ans = INF;
for(int i = 1; i <= n; ++i) {
if(S >= V[i]) ans = min(ans, dpmin(S - V[i]) + 1);
}
return ans;
}
void print_ans(int s)
{
for(int i = 1; i <= n; i++)
{
if(s >= V[i] && d[s] == d[s - V[i]] + 1)
{//printf("%d:%d -- %d:%d\n",s,d[s],s-V[i],d[s-V[i]]);
printf("%d ",V[i]);
print_ans(s-V[i]);
break;
}
}
}
int main() {
memset(vis, 0, sizeof(vis));
cin >> n >> S;
for(int i = 1; i <= n; ++i) {
cin >> V[i];
}
vis[0] = 1;
d[0] = 0;
cout << dpmax(S) << endl;
print_ans(S);
printf("\n");
memset(vis, 0, sizeof(vis));
vis[0] = 1;
d[0] = 0;
cout << dpmin(S)<< endl;
print_ans(S);
printf("\n");
return 0;
}
/*
3 12
3 4 5
*/