[PTA] Find More Coins

   

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;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值