PAT-A 1068. Find More Coins (30) DP和DFS两种方法实现

题目大意:用n个硬币买价值为m的东西,输出使用方案,使得正好几个硬币加起来价值为m。从小到大排列,输出最小的那个排列方案

DP解法:

01背包问题,因为要输出从小到大的排列,可以先把硬币面额从大到小排列,然后用bool类型的choice[i][j]标记数组dp[i][j](dp[i][j]的理解是前i个硬币组成面额为j的值)是否选取,如果选取了就令choice为true;然后进行01背包问题求解,如果最后求解的结果不是恰好等于所需要的价值的,就输出No Soultion,否则从choice[i][j]判断选取的情况,index从n到1表示从后往前看第i个物品的选取情况,v从m到0表示从容量m到0是否选取(v = v– w[index]),把选取情况压入arr数组中,最后输出arr数组

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int dp[10010], w[10010];
bool choice[10010][10010];
int cmp1(int a, int b){return a > b;}//由大到小排序 
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)
        scanf("%d", &w[i]);
    sort(w + 1, w + n + 1, cmp1);
    for(int i = 1; i <= n; i++)
	{
        for(int j = m; j >= w[i]; j--)
		{
            if(dp[j] <= dp[j-w[i]] + w[i])
			{
                choice[i][j] = true;
                dp[j] = dp[j-w[i]] + w[i];
            }
//            printf("%d ", dp[j]);
        }
//        printf("\n");
    }
//    for(int i=1; i<=n; ++i)
//	{
//		for(int j=1; j<=m; ++j)
//			printf("%d ", choice[i][j]);
//		printf("\n");
//	}
//	for(int k=1; k<=m; ++k)
//		printf("%d ", dp[k]);
//	printf("\n");
    if(dp[m] != m) printf("No Solution");
    else
	{
        vector<int> arr;
        int v = m, index = n;
        while(v > 0)
		{
            if(choice[index][v] == true)
			{
                arr.push_back(w[index]);
                v -= w[index];
            }
            index--;
        }
        for(int i = 0; i < arr.size(); i++)
		{
            if(i != 0) printf(" ");
            printf("%d", arr[i]);
        }
    }
    return 0;
}

DFS解法:

1、找字典序最小的一种思路是将硬币按面值大小升序排序,这样我们dfs搜出的第一种情况便是答案(可以仔细想想为什么

2、如果就按上面的思路进行深搜会有几个测试点超时TAT,剪枝则是求出第一种情况后,后续搜索直接返回(通过设置标志位判断)。然而,即使是这样在最后一个测试点还是会超时,一个原因是数据量比较大,很皮的是这个情况根本没有solution,于是特判就好啦。求出所有硬币的面值总和sum,如果sum<m,则直接输出”No Solution“


#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e4+10;
int coin[maxn];//存储金币价值的数组 
int n,m;
bool vis[maxn]={false};//深搜时标记数组 
bool flag = false;//递归边界标志 
vector<int> ansPath;//保存ans 
int k;//k用来存储能用到的最大的金币的价值 
void dfs(int cur, vector<int> path, int w)//参数1 表示 第i枚金币;参数2 表示 ans数组;参数3表示已经组合到的金币的价值 
{
	if(flag) return;//如果flag为 true,直接结束。 
	vis[cur] = true;//将当前金币标记 为已访问(上次递归前已经加入到path数组 25行)
	//初始时cur为0,这个0号金币没加入path数组(前面没递归调用)。 
	if(w > m) return;//组合价值超过 要买的商品的价值m,直接结束 
	if(w == m)
	{
		flag = true;//此处为设置递归结束标志 
		ansPath = path;//找到题解,将其复制到ansPath全局数组中 
		return;
	}
	for(int i = 1; i<=k; i++)
	{
		if(!vis[i])// 如果vis[i] 为 false 
		{
			path.push_back(coin[i]);
			dfs(i, path, w+coin[i]);//递归 
			vis[i] = false;
			path.pop_back();
		}
	}
}
int main()
{
	scanf("%d%d", &n, &m);
	long sum = 0;
	for(int i = 1; i<=n; i++)
	{
		scanf("%d", &coin[i]);
		sum += coin[i];
	}
	if(sum < m) printf("No Solution");
	else {
	
		sort(coin+1, coin+n+1);
		k = n;
		for(int i = 1; i<=n; i++)
			if(coin[i]>=m)
			{
				k = i;//面额比 第k个金币 大的金币都用不到,即k后面的金币都用不到!!! 
				break;
			}
		dfs(0, ansPath, 0);
		if(flag)//如果flag为 true,说明找到满足要求的序列了!!! 
		{
			printf("%d", ansPath[0]);
			for(int i = 1; i<ansPath.size(); i++)
				printf(" %d", ansPath[i]);
		}
		else printf("No Solution");
	}
	
	return 0;
}


猜你喜欢

转载自blog.csdn.net/m0_38024592/article/details/80161262