最近在学习动态规划,PAT中的这一题就是一个典型的dp问题。
这一题和01背包问题很类似,M相当于背包问题中的背包容量,硬币面值相当于每件物品的重量,背包问题中要求物品价值最大,这里要求物品总重(面值和)等于M,从可选方案中选择最优的是根据给定的比较方法。
我们解决问题的难点:如何选择出最小的序列?
首先来看递归式:L(i, j)表示在前i号硬币中选择,并且总价值小于等于j的序列的最大面值和。这里我们不要求等于j,只要尽量接近j就可以了。a[i]是i号硬币的面值,则递归式如下:
L(i, j) = 0
i == 0 || j == 0
= L[i - 1, j]
a[i] > j
= max(L(i - 1, j - a[i]) + a[i], L(i - 1, j))
a[i] <= j
自底向上填表之后,回溯一遍就可以得到所得序列。
但是想要保证序列“最小”,就必须将原面值递减排序。在回溯中每次遇到L(i - 1, j - a[i]) + a[i]这种情况,就决定选择a[i],因为此时a[i]总是最小的。
//类似于01背包的DP //降序排列是关键!!! #include <iostream> #include <vector> #include <algorithm> #include <fstream> using namespace std; int N, M; vector<int> v; vector<vector<int>> l; void dp() { for(int i = 1; i <= N; i++) { for(int j = 1; j <= M; j++) { if(v[i] > j) l[i][j] = l[i - 1][j]; else l[i][j] = l[i - 1][j - v[i]] + v[i] > l[i - 1][j] ? l[i - 1][j - v[i]] + v[i] : l[i - 1][j]; } } if(l[N][M] != M) { cout<<"No Solution"<<endl; return; } //回溯输出选取的序列 int j = M; int i = N; //cout<<" "; while (j > 0) { if(l[i][j] == l[i - 1][j - v[i]] + v[i]) { cout<<v[i]; j -= v[i]; i -= 1; if(j != 0) cout<<" "; } else { i -= 1; } } cout<<endl; } bool cmp(const int &a, const int &b) { return a > b; } int main() { //fstream cin("a.txt"); cin>>N>>M; v.push_back(0); for(int i = 0; i < N; i++) { int tmp; cin>>tmp; v.push_back(tmp); } sort(v.begin() + 1, v.end(), cmp); for(int i = 0; i <= N; i++) { vector<int> tmp; for(int j = 0; j <= M; j++) tmp.push_back(0); l.push_back(tmp); } dp(); }