题解 【Uva】硬币问题
【Uva】硬币问题
Description
有n种硬币,面值分别为v1, v2, ..., vn,每种都有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。
Input
第一行两个整数,n,S(1≤n≤100, 0≤S≤100000)。
第二行n个整数vi-1...n(1≤vi≤S)。
Output
第一行两个整数,分别表示硬币数目的最小值 a 和最大值 b 。无解则输出 -1 。
第二行 a 个整数分别表示使用的是第几种硬币。
第三行 b 个整数分别表示使用的是第几种硬币。
Sample Input
6 12
1 2 3 4 5 6
Sample Output
2 12
6 6
1 1 1 1 1 1 1 1 1 1 1 1
Hint
样例是特殊的,编号和面值是相同的。你编写程序的时候要注意输出编号而不是面值。
结果按编号升序输出字典序小一种。
Source
入门经典,DP,DAG
解析
这题一看就要用DP啊啊!
然而却不知道怎么用qwq!!!
其实可以把它看成是DAG,
每一个点(即不同面值)可以通过许多条权值为1的边到达其它点(即增加一枚硬币)。
因此,只要求最短路和最长路就行了。
另外,硬币的方案可以用一个father数组来记录,只要在更新路径的时候一起更新就行了。
上AC代码:
#include <bits/stdc++.h> using namespace std; int n,s,v[101],num[100001]; int dmin[100001]/*最小路径长度*/,famin[100001]/*上一个硬币*/,ansmin[100001]/*打印路径*/,mincnt=0/*硬币个数*/; int dmax[100001],famax[100001],ansmax[100001],maxcnt=0;/*max和min一样(下同)*/ int que[10000001],vis[100001]; int main(){ scanf("%d%d",&n,&s); for(int i=1;i<=n;i++){ scanf("%d",&v[i]); num[v[i]]=i; } vis[0]=1; memset(dmin,0x3f,sizeof(dmin)); memset(dmax,-0x3f,sizeof(dmax)); dmin[0]=0;dmax[0]=0; for(int i=0;i<=s;i++)/*枚举面值*/{ for(int j=1;j<=n;j++)/*枚举到达当前面值的方式*/{ int k=i-v[j]; if(k<0) break; if(dmin[i]>dmin[k]+1)/*更新*/{ dmin[i]=dmin[k]+1; famin[i]=k; } if(dmax[i]<dmax[k]+1){ dmax[i]=dmax[k]+1; famax[i]=k; } } } if(dmin[s]==0x3f3f3f3f||dmax[s]==0){ printf("-1\n"); return 0; } printf("%d %d\n",dmin[s],dmax[s]); for(int i=s;i;i=famin[i])/*打印路径*/{ int k=famin[i]; ansmin[++mincnt]=num[i-k]; } sort(ansmin+1,ansmin+mincnt+1);//这一步似乎不需要(重构时懒得删了) for(int i=1;i<=mincnt;i++) printf("%d ",ansmin[i]); printf("\n"); for(int i=s;i;i=famax[i]){ int k=famax[i]; ansmax[++maxcnt]=num[i-k]; } sort(ansmax+1,ansmax+maxcnt+1); for(int i=1;i<=maxcnt;i++) printf("%d ",ansmax[i]); printf("\n"); }