1.深度优先搜索
题意是给定总金额,硬币数值,输出恰好能够凑出总金额的硬币组合方法,由于是多解题,要求按字典序最小输出,处理这个字典序只需实现对数组进行排序,为了方便直接调用了stl::sort,然后在搜索的时候加上一些限定条件。
注意要考虑到重复金额的硬币,并由此作相应的剪枝操作,最后一个测试点估计含有大量无效重复数据,未给出恰当的剪枝优化就很容易TLE。
#include<stdio.h>
#include<stdlib.h>
#include<algorithm>
#define MAX 10005
using namespace std;
int n, m; //n代表硬币数目,m代表给定总金额
int a[MAX]; //每个硬币的价值
int path[MAX]; //存储结果
bool flag; //是否找到解
void dfs(int x, int y) // x代表当前深度剩下金额,y代表当前深度使用的硬币下标
{
if (flag)return; //剪枝1:已经找到最优解,返回
int i;
static int layer = 0; // 记录当前层数
path[layer] = a[y]; // 暂存结果
if (x == 0) { //当前深度剩下金额为0时,搜索成功,输出答案并返回
flag = true; // 设置找到解为真
for (i = 1; i<layer; i++) {
printf("%d ", path[i]);
}
printf("%d\n", path[layer]);
return;
}
//硬币已按金额从小到大排序
for (i = y+1; i<n; i++) { // 剪枝2:从下一个更大金额的硬币开始搜索(注意不需要从头重新搜索)
if (x - a[i] >= 0) { // 剪枝3:如果下一个硬币金额小于等于剩下金额,使用下一硬币
layer++; // 进入时层数加一
dfs(x - a[i], i); //继续搜索
layer--; //退出时层数减一
}
else break; //剪枝4:如果下一个更大金额的硬币已经大于剩下金额,剩下的硬币也一定比它大,所以不需要继续搜索
}
}
int main()
{
//freopen("test.txt", "r", stdin);
int i;
int sum = 0;
flag = false;
scanf("%d%d", &n, &m);
for (i = 0; i<n; i++) {
scanf("%d", &a[i]);
sum += a[i];
}
if (sum < m) {
printf("No Solution\n");
return 0;
}
sort(a, a + n); //按从小到大排序,因为需要字典序输出,所以优先找小的硬币
dfs(m, -1);
if (!flag)printf("No Solution\n");
//system("pause");
return 0;
}
2.动态规划
状态转移方程:dp[ i ] [ j ] = max ( dp[ i - 1 ][ j - a[ i ] ] + a[ i ], dp[ i - 1 ][ j ] )
dp[ i ][ j ]代表在处理第i个硬币时,期望金额为j。存在两种可能性,用到了第i个硬币,没有用到第i个硬币。如果没有用到第i个硬币,说明在前一种情况,也就是处理i-1个硬币且期望金额为j,是当前的最优解。如果用到了,就要回到期望金额为 j - a[i ]的情况,并加上第i个硬币的价值,为当前最优解。判断是否使用第i个硬币由这两个值哪个更大决定。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<algorithm>
#define MAX 10005
using namespace std;
int n, m; //n代表硬币数目,m代表给定总金额
int a[MAX]; //每个硬币的价值
int dp[MAX][105]; //二维背包,dp[i][j]代表在处理第i个硬币时,期望金额为j
//(dp[i][j]的值代表实际的金额,它不一定等于期望金额,这取决于我们能否恰好凑够硬币)
bool flag[MAX][105]; //判断哪种状态为解,便于以后的输出判断
void solve()
{
memset(dp, 0, sizeof(dp)); //初始化背包为0
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
int t = j - a[i] < 0 ? 0 : dp[i - 1][j - a[i]] + a[i];
//判断第i个硬币的价值是否大于当前期望金额的价值,
//如果大于的话我们不能使用该硬币凑,设为0,否则就
//回到处理i-1个硬币时,期望金额恰好为当前期望金额
//减去第i个硬币价值时的状态,用该状态进行递推
//判断是否使用当前硬币,判断条件是在同样的期望金额下,
//使用当前硬币和不使用当前硬币哪个实际值更大(更接近于期望金额)
if (dp[i - 1][j]>t)dp[i][j] = dp[i - 1][j];
else {
dp[i][j] = t;
flag[i][j] = true;//如果使用了当前金额,把这个状态标记为真,方便以后输出
}
}
}
}
bool cmp(int x, int y) {
return x>y;
}
int main()
{
int i;
int ans[105];
int cnt = 0;
memset(flag, false, sizeof(flag));//状态初始化为假
scanf("%d%d", &n, &m);
for (i = 1; i<=n; i++) {
scanf("%d", &a[i]);
}
sort(a + 1, a + n + 1,cmp );
//从大到小进行排序,因为动态规划和搜索刚好是从两个不同方向解决问题,
//前者是自底向上,后者是自顶向下,搜索需要先搜索小的来满足字典序
//而动态规划需要以大的为基础向后推,最终到顶还是小的数值
solve();
if (dp[n][m] != m) { //当实际金额不等于期望金额时,无解
printf("No Solution\n");
}
else { //为了格式化输出将结果存到ans数组中
while (m) { //当前金额不为0时
while (!flag[n][m])n--; //找到下一个使用的硬币
ans[cnt++] = a[n];
m = m - a[n]; //减去使用的金额
n--;
}
for (int i = 0; i < cnt - 1; i++) { //输出
printf("%d ", ans[i]);
}
printf("%d", ans[cnt - 1]);
}
//system("pause");
return 0;
}