您的位置:首页 > 其它

硬币问题(DAG上的动态规划)

2013-05-01 18:45 239 查看
问题描述:

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

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

代码:

#include<stdio.h>
#define N 1100
int v
,min
,max
,min_coins
,max_coins
;

void print_ans(int *d,int s, int n) {
while(s){
printf("%d ",v[d[s]]);
s-=v[d[s]];
}
printf("\n");
}
int main() {
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]);
min[0]=max[0]=0;
for(i=1;i<=s;i++){
min[i]=0x7FFFFFFF;
max[i]=-0x7FFFFFFF;
}
for(i=1;i<=s;i++){
for(j=0;j<n;j++){
if(i>=v[j]){
//min[i]=min[i]<(min[i-v[j]]+1)?min[i]:(min[i-v[j]]+1);
if(min[i]>min[i-v[j]]+1){
min[i]=min[i-v[j]]+1;
min_coins[i]=j;
}
if(max[i]<max[i-v[j]]+1){
max[i]=max[i-v[j]]+1;
max_coins[i]=j;
}
//max[i]=max[i]>(max[i-v[j]]+1)?max[i]:(max[i-v[j]]+1);
}
}
}
printf("%d %d\n",min[s],max[s]);
print_ans(min_coins,s,n);
print_ans(max_coins,s,n);
}
return 0;
}

上面的代码中,如果不需要输出路径的话,则可以不要max_coins和min_coins数组,下面在写一个递归的。

#include<stdio.h>
#include<string.h>
#define N 1100

int v
,d
,vis
;

int dp(int s, int n) {
int i;
if(vis[s])
return d[s];
vis[s]=1;

d[s]=-1<<30;//没有算过的我们假定很小
for(i=0;i<n;i++)
if(s>=v[i]&&d[s]<dp(s-v[i],n)+1)
d[s]=dp(s-v[i],n)+1;
return d[s];
}
void print_ans(int s,int n){
int i;
for(i=0;i<n;i++){
if(s>=v[i]&&d[s]==d[s-v[i]]+1){
printf("%d ",v[i]);//输出所选的银币面值
print_ans(s-v[i],n);
break;
}
}
}
int main() {
int T,i,n,s,ans;
scanf("%d",&T);
while(T--) {
scanf("%d %d",&n,&s);
for(i=0;i<n;i++) {
scanf("%d",&v[i]);
}
memset(vis,0,sizeof(vis));
vis[0]=1;
d[0]=0;//终点状态药初始化为0,访问过
ans=dp(s,n);
printf("%d\n",ans);
print_ans(s,n);
printf("\n");
}
return 0;
}


这个我只写了求最长路径的,如果要求最短路径,则和最长路径类似,在此省略。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: