硬币问题
有n种硬币,面值分别V1,V2,V3,…,Vn,每种有无限多。给定非负整数S,可以选用多少个硬币,使得面值之和恰好为S?输出硬币数目的最小值和最大值。
1<=n<=100 0<=S<=10000 1<=Vi<=S
解题思路
最长路与最短路问题
int dp_max(int S,int v[],int n)
{
int *ans=&dma[S]; //ans和dma共为一个变量 c++用法:int &ans=dma[S]
int i;
if(*ans!=-1) return *ans;
*ans=-(2<<29); //让无法取得0的情况不影响结果
for(i=0;i<n;i++)
if(S>=v[i])
*ans=Max(*ans,dp_max(S-v[i],v,n)+1);
return *ans;
}
要注意保存最长路径的数组dma不能为0因为有的路径长度是可以为0的,如dma【0】,所以dma要初始化为-1,dma[0]初始化为0.
还要注意ans:ans不能为0或过大的数,若是无法达到最终状态0但是路径过长的话可能会影响结果,所以我们ans的初始化值要很小,不能影响到结果(求最短路同理,只不过ans要很大罢了)
递归解法:
#include <stdio.h>
#include <stdlib.h>
#define Max(a,b) (a)>(b)?(a):(b)
#define Min(a,b) (a)<(b)?(a):(b)
#define N 103
#define M 10003
int dma[M],dmi[M];
int dp_max(int S,int v[],int n)
{
int *ans=&dma[S]; //ans和dma共为一个变量 c++用法:int &ans=dma[S]
int i;
if(*ans!=-1) return *ans;
*ans=-(2<<29); //让无法取得0的情况不影响结果
for(i=0;i<n;i++)
if(S>=v[i])
*ans=Max(*ans,dp_max(S-v[i],v,n)+1);
return *ans;
}
int dp_min(int S,int v[],int n)
{
int i;
int *ans=&dmi[S];
if(*ans!=-1)
return *ans;
*ans=(2<<29); //为了让无法取得0的情况不影响结果
for(i=0;i<n;i++)
if(S>=v[i])
*ans=Min(*ans,dp_min(S-v[i],v,n)+1);
return *ans;
}
int main()
{
int n,i,S;
int v[N];
scanf("%d%d",&n,&S);
for(i=0;i<n;i++)
scanf("%d",&v[i]);
for(i=1;i<=S;i++) //初始化注意d[0]=0
{
dma[i]=-1;
dmi[i]=-1;
}
printf("max:%d min:%d\n",dp_max(S,v,n),dp_min(S,v,n));
return 0;
}
递推解法:
动态转移方程 更新状态方程:dp【i】=max/min(dp【i】,dp【i-v【i】】+1)
#include <stdio.h>
#include <stdlib.h>
#define INF (2<<29)
#define M 10003
#define N 103
#define Max(a,b) (a)>(b)?(a):(b)
#define Min(a,b) (a)<(b)?(a):(b)
int main()
{
int minv[M],maxv[M],i,j;
minv[0]=maxv[0]=0;
int S,n,v[N];
scanf("%d%d",&n,&S);
for(i=0;i<n;i++)
scanf("%d",&v[i]);
for(i=1;i<=S;i++)
{
minv[i]=INF;
maxv[i]=-INF; //防止无法达到最终情况的路径影响结果
}
for(i=1;i<=S;i++)
for(j=0;j<n;j++)
{
if(i>=v[j])
{
maxv[i]=Max(maxv[i],maxv[i-v[j]]+1);
minv[i]=Min(minv[i],minv[i-v[j]]+1);
}
}
printf("max:%d min:%d",maxv[S],minv[S]);
return 0;
}
递归最小字典序:
#include <stdio.h>
#include <stdlib.h>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
#define N 103
#define M 10003
int dma[M],dmin[M];
int v[N],n;
int dp_max(int S)
{
int *ans=&dma[S],i;
if(*ans!=-1) return *ans;
*ans=-(2<<30);
for(i=0;i<n;i++)
if(S>=v[i])
*ans=Max(*ans,dp_max(S-v[i])+1);
return *ans;
}
int dp_min(int S)
{
int *ans=&dmin[S],i;
if(*ans!=-1)
return *ans;
*ans=2<<29;
for(i=0;i<n;i++)
if(S>=v[i])
*ans=Min(*ans,dp_min(S-v[i])+1);
return *ans;
}
void printf_ans_max(int S)
{
int i;
for(i=0;i<n;i++)
{
if(S>=v[i] && dma[S]==dma[S-v[i]]+1) //d[S]不会为负数故不会找到无法到最终情况结果
{
printf("%d ",v[i]);
printf_ans_max(S-v[i]);
break; //防止回溯后继续遍历
}
}
}
void printf_ans_min(int S)
{
int i;
for(i=0;i<n;i++)
{
if(S>=v[i] && dmin[S-v[i]]+1==dmin[S])
{
printf("%d ",v[i]);
printf_ans_min(S-v[i]);
break;
}
}
}
int main()
{
int S,i;
while(~scanf("%d%d",&n,&S))
{
for(i=1;i<=S;i++)
dma[i]=dmin[i]=-1; //初始化
for(i=0;i<n;i++)
scanf("%d",&v[i]);
printf("max:%d min:%d\n",dp_max(S),dp_min(S));
printf("max:");
printf_ans_max(S);
putchar('\n');
printf("min:");
printf_ans_min(S);
putchar('\n');
}
return 0;
}
动态规划 最小字典序
#include <stdio.h>
#include <stdlib.h>
#define M 10003
#define N 103
int maxv[M],minv[M];
int max_coin[M],min_coin[M];
int v[N];
void printf_ans(int S,int *coin)
{
while(S)
{
printf("%d ",v[coin[S]]);
S-=v[coin[S]];
}
}
int main()
{
int S,n,i,j;
while(~scanf("%d%d",&n,&S))
{
for(i=1;i<=S;i++)
{
maxv[i]=-(2<<29);
minv[i]=(2<<29);
}
for(i=0;i<n;i++)
scanf("%d",&v[i]);
for(i=1;i<=S;i++)
for(j=0;j<n;j++)
{
if(i>=v[j])
{
if(maxv[i]<maxv[i-v[j]]+1)
{
maxv[i]=maxv[i-v[j]]+1;
max_coin[i]=j;
}
if(minv[i]>minv[i-v[j]]+1)
{
minv[i]=minv[i-v[j]]+1;
min_coin[i]=j;
}
}
}
printf("max:%d min:%d\n",maxv[S],minv[S]);
printf("max:");
printf_ans(S,max_coin);
putchar('\n');
printf("min:");
printf_ans(S,min_coin);
putchar('\n');
}
return 0;
}