有多组数据,对于每组数据,首先是要求凑成的邮票总值M,M<100。然后是一个数N,N〈20,表示有N张邮票。接下来是N个正整数,分别表示这N张邮票的面值,且以升序排列。
对于每组数据,能够凑成总值M的最少邮票张数。若无解,输出0。
10 5 1 3 3 3 4
3
//第二次做这个题目了,比第一次更难受 //这次用的是动态规划 #include<iostream> #include<vector> #define mmax 0x7fffffff using namespace std; int min(int a,int b){return a<b?a:b;} int main() { int m,n; while(cin>>m) { /*****************读取数据区域****************************/ cin>>n; vector<int> c(n); for(int i=0;i<n;i++) cin>>c[i]; /*****************读取数据区域****************************/ /*****************初始化区域****************************/ vector<int> dp(m+1); for(int i=0;i<=m;i++) dp[i]=mmax; dp[0]=0; /*****************初始化区域****************************/ /*****************循环体区域****************************/ /*方法大概类似于筛选法, 题目中已经给出有序,不需要再次排序, 对于每一个邮票来说,有且仅有一次使用机会 所以遍历所有邮票,获取邮票的面值, 对于每一个dp数组来说,dp[i]中的i代表总值,dp[i]中保存的数据为个数, 遍历所有小于等于m的i,如果总值减去当前所选的邮票的面值,即i-c[i]合法并且可达 那么i一定可达, 并且dp[i]中的值应该是dp[i-c[i]]的值+1和原来的值中较小的一个。 想想第二层循环,循环所有小于等于m的i时,为什么要倒序?*/ for(int i=0;i<n;i++) { for(int j=m;j>=0;j--) { if(j-c[i]>=0&&dp[j-c[i]]!=mmax) { dp[j]=min(dp[j],dp[j-c[i]]+1); } } } /*****************循环体区域****************************/ /*****************输出数据区域****************************/ if(dp[m]==mmax) cout<<0<<endl; else cout<<dp[m]<<endl; /*****************输出数据区域****************************/ } }
没有采用动态规划,而是尝试了深度优先搜索,代码丑陋,贴出来丰富一下大家的思路
#include<iostream> using namespace std; // M为邮票总值,n为邮票张数,minNum为用到的最少的张数 // N为邮票面额 int M, n, minNum; int N[20]; // index为邮票的编号,Num为当前用到的邮票张数,Sum为用到的邮票总面额 void DFS(int index, int Num, int Sum){ if(index == n){ if(Sum == M && Num < minNum){ minNum = Num; } return; } DFS(index + 1, Num + 1, Sum + N[index + 1]); DFS(index + 1, Num, Sum); } int main(){ // 给minNum一个大于等于20的初值 minNum = 20; while(cin >> M){ cin >> n; for(int i = 0; i < n; i++){ cin >> N[i]; } // 初始index为-1 DFS(-1, 0, 0); if(minNum == 20){ minNum = 0; } cout << minNum << endl; } return 0; }
#include<stdio.h> int dp[20][105],Max=99999999; int main(){ int v[50],N,i,j,M; while(scanf("%d",&M)!=EOF){ for(scanf("%d",&N),i=0;i<N;i++) scanf("%d",&v[i]); for(i=0;i<N;i++) dp[i][0]=0; for(j=0;j<=M;j++) if(j==v[0]) dp[0][j]=1; else dp[0][j]=Max; for(i=1;i<N;i++) for(j=1;j<=M;j++){ dp[i][j]=dp[i-1][j]; if(j>=v[i]&&dp[i][j]>dp[i-1][j-v[i]]+1) dp[i][j]=dp[i-1][j-v[i]]+1; } printf("%d\n",dp[N-1][M]<Max?dp[N-1][M]:0); } }
#include <iostream> #include <math.h> using namespace std; int main(){ int m, n, v[50], dp[20][100], Max = 99999999; while(scanf("%d",&m)!=EOF&&m>0){ scanf("%d", &n); if(n==0){ printf("0\n"); break; } for(int i=1;i<=n;i++) scanf("%d",&v[i]); for(int i=0;i<=m;i++) dp[0][i] = Max; for(int i=0;i<=n;i++) dp[i][0] = 0; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ if(v[i]>j) dp[i][j] = dp[i-1][j]; else dp[i][j] = min(1+dp[i-1][j-v[i]], dp[i-1][j]); } if(dp[n][m]==Max) printf("0\n"); else printf("%d\n",dp[n][m]); } }
dfs,bfs都可以,速度都差不多
package com.speical.first;
import java.util.Scanner;
/**
* 邮票问题
*
* dfs
* 因为一张有票可以使用一次,所以dfs其实也挺快的
* 不用变量来维持就小
* 根据dfs特性,最后一个成功的,必然是数量最小的那个路径
* @author special
* @date 2017年12月21日 下午3:54:17
*/
public class Pro13 {
private static int sum;
private static int[] nums;
private static int minCount;
public static void dfs(int index, int add, int count){
if(add == sum) {
minCount = count;
return;
}
for(int i = index + 1; i < nums.length; i++)
dfs(i, add + nums[i], count + 1);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
while(input.hasNext()){
sum = input.nextInt();
int n = input.nextInt();
nums = new int[n];
for(int i = 0; i < n; i++)
nums[i] = input.nextInt();
minCount = 0;
dfs(-1,0,0);
System.out.println(minCount);
}
}
}
#include <iostream>
using namespace std ;
#define maxn 110
#define maxm 999999
int a[ maxn ], dp[ maxn ];
void init(){
for ( int i= 0 ;i< maxn ;i++) dp [i]= maxm ;
}
int main(){
int n,m;
while ( cin >>m>>n){
init ();
for ( int i= 0 ;i<n;i++) cin >> a [i];
dp [ 0 ]= 0 ;
for ( int i= 0 ;i<n;i++){
for ( int j=m;j>= a [i];j--){
dp [j]= min ( dp [j], dp [j- a [i]]+ 1 );
}
}
cout <<( dp [m]== maxm ? 0 : dp [m])<< endl ;
}
return 0 ;
}
#include<iostream> using namespace std; const int N = 20; int k; int value[N]; void DFS(int count,int n,int deep,int index) { if (n == 0) k = min(deep, k); else { for (int i = index; i < count; i++) //每个元素只会访问一次,减少访问序列 { if (n - value[i] < 0) //数据是升序,大于某数其他的数的序列不访问 break; DFS(count, n - value[i], deep + 1,i+1); } } } int main() { int n, count; while (cin >> n >> count) { k = count + 1; for (int i = 0; i < count; i++) cin >> value[i]; DFS(count, n, 0,0); if(k!=count+1) cout << k << endl; else cout << 0 << endl; } }
#include<iostream> #include<algorithm> using namespace std; int main() { int M, N; while (cin>>M>>N) { int* P = new int[N];//面值 for (int i = 0; i < N; i++) cin >> P[i]; //dp数组表示将第i个邮票加入集合后 凑总量为j的面额 所需要的最少邮票数量 int* dp = new int[M + 1]; //base case for (int j = 0; j <= M; j++) dp[j] = M; dp[0] = 0; //状态转移 for (int i = 0; i < N; i++) for (int j = M; j >= P[i]; j--) dp[j] = min(dp[j], dp[j - P[i]] + 1); if (dp[M] == M)//说明所有邮票加起来也凑不出来 dp[M] = 0; cout << dp[M] << endl; delete[] P, dp; } return 0; }
dp[i][j] = min(dp[i-1][j], dp[i-1][j-value[i]]+1);在最后输出时,如果无解的话,dp肯定要大于upper的。所以对于 dp[n][m]>=upper,输出0表示无解
#include <iostream> #include <cstdio> #include <cmath> #include <algorithm> #include <climits> #include <cstring> using namespace std; int value[21]; int dp[21][101]; //dp[i][j]表示总值为j时,给定i张邮票所需要的最少邮票 // dp[i][j] = min(dp[i-1][j], dp[i-1][j-value[i]]+1) bool cmp(int a, int b){ return a>b; } int main(){ int m, n; while(cin>>m>>n && m && n){ for(int i=1; i<=n; i++){ cin>>value[i]; } int upper = n +100;//随便挑的一个最大值,只要比n大就行。保证正确答案永远取不到这个值 for(int i= 0; i<=n; i++){ for(int j=0; j<=m; j++){ //下面这一对if else的顺序不能调换! if (j==0){//如果目标总值为0,不需要拿邮票,就是最小值0 dp[i][j]=0; continue; } else if(i==0){//如果没有给邮票,那要达到目标总值肯定无解,定为需要无限张邮票 dp[i][j]=upper; continue; } if(j<value[i]){//装不下 dp[i][j] = dp[i-1][j]; } else{ //要么和前面一样,要么拿当前这张 dp[i][j] = min(dp[i-1][j], dp[i-1][j-value[i]]+1); } } } // for(int i=0; i<=n; i++){ // for(int j=0; j<=m; j++){ // cout<<dp[i][j]<<" "; // } // cout<<endl; // } if(dp[n][m]>=upper)//正确答案取不到的值,即无解 cout<<0<<endl; else cout<<dp[n][m]<<endl; } return 0; }
dp[j] = min(dp[j], dp[j-v[i]]+1);注意:j一定是从后向前遍历,因为0-1背包只能取1次。从后遍历的话可以保证dp[j]在j之前的状态都是不含本件物品的。
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 21; const int MAXM = 101; const int INF = 21; int v[MAXN]; int dp[MAXM]; int n,m; int main(){ while(cin>>m){ cin>>n; for(int i=1; i<=n; i++){ cin>>v[i]; } fill(dp, dp+MAXM, INF); dp[0] = 0; for(int i=1; i<=n; i++){ for(int j=m; j>=v[i]; j--){ dp[j] = min(dp[j], dp[j-v[i]]+1); } } if(dp[m]<INF) cout<<dp[m]<<endl; else cout<<0<<endl; // for(int i=0; i<=m; i++){ // cout<<dp[i]<<" "; // } // cout<<endl; } return 0; }
// 由于样本数据集较小,采用dsp的方法写的 #include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn = 20; int value[maxn] = {0}; // 存储邮票的面值 int main() { int m; int n; while(cin >> m) { cin >> n; for(int i = 0; i < n; ++i) { cin >> value[i]; } // 0-2^n-1 int end = (1<<n) - 1; int count = -1; int sum = 0; int tmp = 0; for(int i = 0; i <= end; ++i) { sum = 0; tmp = 0; // cout << i << ":"; for(int j = 0; j < n; ++j) { // cout << (i & (1 << j)) << " "; if(i & (1 << j)) { sum += value[j]; tmp ++; } } // cout << endl; if(sum == m) { if(count == -1) { count = tmp; } else { count = count < tmp ? count : tmp; } } } if(count == -1) { cout << 0 << endl; } else { cout << count << endl; } } return 0; }
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int MAXN =101; const int INF = 9999; int stamp[MAXN]; int dp[MAXN]; int main() { int m,n; while(scanf("%d%d",&m,&n) != EOF) { for(int i=0; i<n; ++i) { scanf("%d",&stamp[i]); } //sort(stamp,stamp+n); for(int i=0; i<=m; ++i) { if(i == 0) { dp[i] = 0; } else { dp[i] = INF; } } for(int i=0; i<n; ++i) { for(int j=m; j>=stamp[i]; --j) { dp[j] = min(dp[j],dp[j-stamp[i]]+1); } } if(dp[m] != INF) { printf("%d\n",dp[m]); } else { printf("0\n"); } } return 0; }
首先,这是一个背包问题。描述转化为“背包”,有N个物品,每个物品的质量为Vi,背包重量为M,求最少取多少个物品才能刚好装满背包,如果无法满足条件,返回0。
我们用INT32_MAX表示无法凑齐,设dp[i][j]为邮票为前i张时,刚好凑成j所需要的最小邮票张数,有以下状态公式:
状态公式的解释如下:
// // Created by jt on 2020/8/26. // #include <iostream> #include <vector> using namespace std; int main() { int M, N; cin >> M >> N; vector<int> v; for (int i = 0; i < N; ++i) { int a; cin >> a; v.push_back(a); } vector<vector<int> > dp(N + 1, vector<int>(M + 1, INT32_MAX)); for (int i = 0; i <= N; ++i) { dp[i][0] = 0; } for (int i = 1; i <= N; ++i) { for (int j = 1; j <= M; ++j) { // 如果可以选择当前邮票 if (j >= v[i-1] && dp[i-1][j-v[i-1]] != INT32_MAX) { dp[i][j] = min(dp[i-1][j-v[i-1]] + 1, dp[i-1][j]); } else { dp[i][j] = dp[i-1][j]; } } } if(dp[N][M] == INT32_MAX) cout << 0 << endl; else cout << dp[N][M] << endl; }
#include<vector> (721)#include<iostream> using namespace std; int main() { int m, n; while(cin>>m>>n) { vector<int> stamps(n, 0); for(int i=0; i<n; ++i) cin>>stamps[i]; vector<vector<int>> dp(n+1, vector<int>(m+1)); for(int i=0; i<=m; ++i) dp[0][i]=10000; for(int i=0; i<=n; ++i) dp[i][0]=0; for(int i=1; i<=n; ++i) { for(int j=1; j<=m; ++j) { if(stamps[i-1] <= j) dp[i][j]=min(dp[i-1][j], dp[i-1][j-stamps[i-1]]+1); else dp[i][j]=dp[i-1][j]; } } if(dp[n][m]==10000) cout<<0<<endl; else cout<<dp[n][m]<<endl; } }
#include <stdio.h> int a[20]; int count(int m,int top) { int min=20; for(int i=top; i>=0; i--) { if(a[i]==m) { return 1; } if(a[i]<m) { int temp=count(m-a[i],i-1); if(temp && temp<min) { min=temp; } } } if(min-20) { return min+1; } return 0; } int main() { int m,n; while(scanf("%d%d",&m,&n)!=EOF) { for(int i=0; i<n; i++) { scanf("%d",&a[i]); } printf("%d\n",count(m,n-1)); } return 0; }
#include<stdio.h> #include<stdlib.h> #include<string.h> #pragma warning(disable:4996) int main() { int sum; int num; int* value; int score[101];//(序号是邮票总分值),(值是邮票个数) while (scanf("%d", &sum) != EOF) { for (int i = 0; i < 101; ++i) score[i] = -1; //score[0]设为0 score[0] = 0; scanf("%d", &num); value = (int*)malloc(sizeof(int)*num); for (int i = 0; i < num; ++i) scanf("%d", value + i); //前面都是准备活动 //正菜,从第一张邮票遍历到最后一张 for (int i = 0; i < num; ++i) { for (int j = 100; j >=0; --j)//从后往前遍历score { if (score[j] == -1) continue; if (j + value[i] > 100) { continue; } if (score[j + value[i]] == -1)//能组成的新的总分值 score[j + value[i]] = score[j] + 1; else if (score[j + value[i]] > score[j] + 1)//有更小的邮票个数 score[j + value[i]] = score[j] + 1; } } if (score[sum] == -1) printf("0\n"); else { printf("%d\n", score[sum]); } free(value); } }
//从后往前遍历,因为不可以有重复的。 #include <bits/stdc++.h> using namespace std; int main(){ int m,n,w[110],dp[1010]={0}; cin>>m>>n; for(int i=1;i<=n;i++){ cin>>w[i]; } for(int i=1;i<=m;i++){ dp[i]=1<<30; } for(int i=1;i<=n;i++){ for(int j=m;j>=w[i];j--){ dp[j]=min(dp[j],dp[j-w[i]]+1); } } if(dp[m]<(1<<30))cout<<dp[m]; else cout<<0; return 0; }
//二维dp数组解法 #include<iostream> #include<vector> using namespace std; int main() { int V, N; while (cin >> V >> N) { vector<int> value(N + 1); vector<vector<int>> dp(N + 1, vector<int>(V + 1)); //初始化dp数组 for (int i = 1; i <= N; i++) cin >> value[i]; //输入每张邮票价值 for (int i = 1; i <= N; i++) { for (int j = 1; j <= V; j++) { if (j == value[i]) //当前价值刚好等于第i张邮票价值 dp[i][j] = 1; else if (j > value[i]) { //当前价值大于第i张邮票价值 if (dp[i - 1][j] && dp[i - 1][j - value[i]]) dp[i][j] = (dp[i - 1][j] < dp[i - 1][j - value[i]] + 1 ? dp[i - 1][j] : dp[i - 1][j - value[i]] + 1); else if (dp[i - 1][j] && !dp[i - 1][j - value[i]]) dp[i][j] = dp[i - 1][j]; else if(!dp[i - 1][j] && dp[i - 1][j - value[i]]) dp[i][j] = dp[i - 1][j - value[i]] + 1; } } } cout << dp[N][V] << endl; } return 0; }
//一维dp数组解法 #include<iostream> #include<vector> using namespace std; int main(){ int V, N, i, j; while(cin >> V >> N){ vector<int> vec(N + 1); vector<int> dp(V + 1, -1); dp[0] = 0; for(i = 1; i <= N; i++) cin >> vec[i]; //输入数据 for(i = 1; i <= N; i++) //遍历每一张邮票 for(j = V; j >= 1; j--) //从大面值向小面值遍历是为了保证钱数“正好” if(j >= vec[i] && dp[j - vec[i]] != -1) if(dp[j] == -1) dp[j] = dp[j - vec[i]] + 1; else dp[j] = (dp[j] > dp[j - vec[i]] + 1 ? dp[j - vec[i]] + 1 : dp[j]); if(dp[V] == -1) cout << 0 << endl; else cout << dp[V] << endl; } return 0; }
/* 动态规划,01背包问题,这次求的是最小值 状态表示 f[i][j] 只从前i张邮票里面选,总价值>=j,花的最小邮票的数量 状态计算 1.如果j=0,那么无解,如果i=0,那么也无解,结果为0 2.将集合分为包含第i张的和不包含第i张的 如果不包含第i张,那么f[i][j]=f[i-1][j] 如果包含第i张,那么f[i][j] = min(f[i][j],f[i-1][j-v[i]]+w[i]); 所以状态计算就是f[i][j] = min(f[i-1][j],f[i-1][j-v[i]]+1) 然后再用滚动数组将其优化一下变成一维的就好 */ #include <bits/stdc++.h> using namespace std; const int maxn = 110; const int inf = 1<<30; // 非法状态 int v[maxn]; int f[maxn]; int m,n; int main(){ cin>>m>>n; for(int i=1;i<=n;i++) cin>>v[i]; // input for(int i=1;i<=m;i++) f[i] = inf; // 因为要求的是恰好凑到,那么一开始除了0之外其他都没有合法的解 for(int i=1;i<=n;i++) for(int j=m;j>=v[i];j--) f[j] = min(f[j],f[j-v[i]]+1); if(f[m]<inf) cout<<f[m]<<endl; else cout<<0<<endl; }
//回溯法(dfs) 注意剪枝 #include <iostream> (720)#include <algorithm> #include <vector> using namespace std; (3420)#define INF 0x7F7F7F int m; //邮票面值 int n; //张数 vector <int> v; vector <int> x; int min_n = INF; int select_cnt() { int cnt = 0; for (auto i : x) if (i == 1) cnt++; return cnt; } void dfs(int i, int total, int remain) { if (i == n) { if (total == m && select_cnt() < min_n) min_n = select_cnt(); } else { if (total + remain - v[i] >= m) //不选择 { x[i] = 0; dfs(i + 1, total, remain - v[i]); } if (total + v[i] <= m) //选择 { x[i] = 1; dfs(i + 1, total + v[i], remain - v[i]); } } } int main() { cin >> m >> n; for (int i = 0; i < n; i++) { int num; cin >> num; v.push_back(num); x.push_back(0); } int remain = 0; for (auto i : v) remain += i; dfs(0, 0, remain); min_n = min_n == INF ? 0 : min_n; cout << min_n; }
//动态规划,注意从后往前遍历 #include <iostream> (720)#include <algorithm> #include <vector> using namespace std; (3420)#define INF 0x7F7F7F int m; //邮票面值 int n; //张数 vector <int> v; int func() { vector<int>dp(m + 1); for (int i = 0; i <= m; i++) dp[i] = i == 0 ? 0 : INF; for (int i = 0; i < n; i++) { for (int j = m; j >= 0; j--) { if (j >= v[i]) dp[j] = min(dp[j], dp[j - v[i]] + 1); } } return dp[m]; } int main() { cin >> m >> n; for (int i = 0; i < n; i++) { int num; cin >> num; v.push_back(num); } int res = func(); res = res == INF ? 0 : res; cout << res << endl; }