问题描述:

有n种硬币,面值分别为v1,v2,v3...vn,每种硬币有无限多,给定非负整数s,可以选用多少个硬币,使得面值之和恰好为s?输出硬币数目的最小值和最大值,并且输出各自的选取方案(如果有多种方案,则输出硬币编号字典序较小的方案,输出每种选取方案的面值)。1<=n<=100,0<=s<=10000,1<=Vi<=s.

分析:本质上是一个DAG上的路径问题,我们把每种面值看做一个点,表示还需凑足的面值,则初始状态为0,目标状态为0,若当前在i,则每使用一枚硬币j,状态转移到i-vj。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 1100
int v[N],minm[N],maxm[N],min_coins[N],max_coins[N];
void print_ans(int *d,int s, int n)
{
    while(s)
    {
        printf("%d ",v[d[s]]);
        s-=v[d[s]];
    }
    printf("\n");
}
int main()
{
    //freopen("in.txt","r",stdin);
    int t,i,j,n,s;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&s);
        for(i=0; i<n; i++)
            scanf("%d",&v[i]);
        minm[0]=maxm[0]=0;
        for(i=1; i<=s;i++)
        {
            minm[i]=0x7FFFFFFF;
            maxm[i]=-0x7FFFFFFF;
        }
        for(i=1; i<=s; i++)
        {
            for(j=0; j<n; j++)
            {
                if(i>=v[j])
                {
                    if(minm[i]>minm[i-v[j]]+1)
                    {
                        minm[i]=minm[i-v[j]]+1;
                        min_coins[i]=j;
                    }
                    if(maxm[i]<maxm[i-v[j]]+1)
                    {
                        maxm[i]=maxm[i-v[j]]+1;
                        max_coins[i]=j;
                    }
                }
            }
        }
        printf("%d %d\n",minm[s],maxm[s]);
        print_ans(min_coins,s,n);
        print_ans(max_coins,s,n);
    }
    return 0;
}