【题目描述】
题目大意是:邮票发行商会发行不同面值、不同种类的邮票给集邮爱好者,集邮爱好者有总目标面额,通过不同的邮票组合(总数在4张以内)达到该面值,卖给集邮爱好者。另外,发行商发行的邮票面值最多25种,但有可能同种面值有好几种邮票,甚至超过25种。
最佳方案满足以下要求:
【思路分析】
1. 邮票种类存储策略
由于最多只有四张邮票给集邮爱好者,我们可以设定同一面值的邮票最多存储5种,因为再多的种类组成的总价值也是一样的,输出的格式也一样。
这里取5种是因为要判断是否有tie的情况,设想某种邮票有N种(N>4),且这N种中任取4种为最优解时,此时应该输出”tie”,而如果只取4种,是不会输出”tie”。
所以,25种面值*最多存储的5种=125,为邮票的存储空间。HasMaxSameStamp函数判断该面值邮票是否已经达到5种。
int stamps[126]; // 存储邮票种类 int stampType; // 邮票种类数 bool HasMaxSameStamp(int newstamp) { int hasSameNum = 0; for (int i(0); i < stampType; i++) { if (stamps[i] == newstamp) { hasSameNum += 1; if (hasSameNum >= 5) return true; } } return false; }
2. 深度优先法搜索所有解
解为最多四张邮票,用int solution[4]暂时存储进行搜索,noOfStamp为当前考虑的solution[noOfStamp]中的邮票,lastIndex为了避免重复搜索,将不搜索已搜索过的邮票定为策略,其意义为接下来的解只从数组编号为lastIndex的邮票开始搜索。
void FindStampsCombination(int solution[4], int noOfStamp, int lastIndex) { if (IsASolution(solution)) { int *p = new int[4]; memcpy(p, solution, sizeof(int)*4); solutions.push_back(p); return ; } if (noOfStamp >= 4) // 邮票数已经为4张,应该被剪枝,回溯 return ; for (int stampindex( lastIndex ); stampindex < stampType; stampindex++) { solution[noOfStamp] = stampindex; FindStampsCombination(solution, noOfStamp+1, stampindex); solution[noOfStamp] = -1; // 重置该位向量 } }
3. 找出最优解
将所有解存入vector<int*> solutions中,以题目要求的比较策略进行排序比较。先比较类型数,更多的取胜;再比较数量,少的取胜;最后比较单张面值最大的。
typedef struct solutionattributes { int No; int Types; int Number; int Max; }SolutionAttributes; int CompareSolution(const void* a, const void* b) { SolutionAttributes *pa = (SolutionAttributes*)a; SolutionAttributes *pb = (SolutionAttributes*)b; if ((*pa).Types != (*pb).Types) { return (*pb).Types - (*pa).Types; } else if ((*pa).Number != (*pb).Number) { return (*pa).Number - (*pb).Number; } else { return (*pb).Max - (*pa).Max; } }
【小结】
本题关注的要点在于:DFS搜索(及剪枝、回溯),比较函数的编写规则,以及集合set的用法。
1. 回溯法的程序编写见本题的“2.深度优先法搜索所有解”;
2. 比较函数规则:参数a, b,如果返回大于0,则a到b的后面去,记住这一原则即可;
3. 集合set的用法:
set<int> myset; myset.insert(10); // 插入 set<int>::iterator set_it = myset.find(11); // 查找 myset.count(7); // 计数 myset.erase(10); // 删除 // 遍历 set<int>::iterator it; //定义前向迭代器 // 反向迭代器:set<int>::reverse_iterator for(it = myset.begin(); it != myset.end(); it++) { //... }
【附:完整源码】
#include <iostream> #include <vector> #include <set> using namespace std; typedef struct solutionattributes { int No; int Types; int Number; int Max; }SolutionAttributes; int stamps[126]; // 存储邮票种类 int stampType; // 邮票种类数 int customer; vector<int*> solutions; void InitialForStamps(); bool HasMaxSameStamp(int newstamp); void FindStampsCombination(int solution[4], int noOfStamp, int lastIndex); bool IsASolution(int s[4]); int Compare(const void* a, const void* b); void OutputBestSolution(); int CountTypes(int s[4]); int CountNumber(int s[4]); int CountMax(int s[4]); int CompareSolution(const void* a, const void* b); int main() { int stampValue; while (scanf("%d", &stampValue) != EOF) { // 读取邮票种类 InitialForStamps(); while (true) { if (stampValue == 0) break; if ( !HasMaxSameStamp(stampValue) ) { stamps[ stampType++ ] = stampValue; } cin>>stampValue; } // 读取顾客需求,并处理,输出 while (true) { cin>>customer; if (customer == 0) break; solutions.clear(); int solution[4] = {-1,-1,-1,-1}; // 四张邮票 FindStampsCombination(solution, 0, 0); OutputBestSolution(); } } return 0; } void InitialForStamps() { memset(stamps, 0, sizeof(stamps)); stampType = 0; } bool HasMaxSameStamp(int newstamp) { int hasSameNum = 0; for (int i(0); i < stampType; i++) { if (stamps[i] == newstamp) { hasSameNum += 1; if (hasSameNum >= 5) return true; } } return false; } void FindStampsCombination(int solution[4], int noOfStamp, int lastIndex) { if (IsASolution(solution)) { int *p = new int[4]; memcpy(p, solution, sizeof(int)*4); solutions.push_back(p); return ; } if (noOfStamp >= 4) // 邮票数已经为4张,应该被剪枝,回溯 return ; for (int stampindex( lastIndex ); stampindex < stampType; stampindex++) { solution[noOfStamp] = stampindex; FindStampsCombination(solution, noOfStamp+1, stampindex); solution[noOfStamp] = -1; } } bool IsASolution(int s[4]) { int stampsTotalValue = 0; for (int i(0); i < 4; i++) { stampsTotalValue += (s[i] < 0? 0 : stamps[ s[i] ]); } return (stampsTotalValue == customer); } int Compare(const void* a, const void* b) { int *pa = (int*)a; int *pb = (int*)b; return (*pa) - (*pb); } void OutputBestSolution() { SolutionAttributes *sap = new SolutionAttributes[solutions.size()]; for (int i(0); i < solutions.size(); i++) { sap[i].No = i; sap[i].Types = CountTypes(solutions.at(i)); sap[i].Number = CountNumber(solutions.at(i)); sap[i].Max = CountMax(solutions.at(i)); } qsort(sap, solutions.size(), sizeof(sap[0]), CompareSolution); if (solutions.size() == 0) { cout<<customer<<" ---- none"<<endl; } else if (solutions.size() > 1 && sap[0].Types == sap[1].Types && sap[0].Number == sap[1].Number && sap[0].Max == sap[1].Max) { cout<< customer <<" ("<< sap[0].Types <<"): "<<"tie"<<endl; } else { cout<< customer <<" ("<< sap[0].Types <<"):"; int no = sap[0].No; int tempStamps[4] = {-1, -1, -1, -1}; for (int i(0); i < 4; i++) { if (solutions.at(no)[i] != -1) tempStamps[i] = stamps[ solutions.at(no)[i] ]; } qsort(tempStamps, 4, sizeof(tempStamps[0]), Compare); for (int i(0); i < 4; i++) { if (tempStamps[i] >= 0) { cout<<" "<<tempStamps[i]; } } cout<<endl; } } int CountTypes(int s[4]) { set<int> types; for (int i(0); i < 4; i++) { if (s[i] >= 0) types.insert(s[i]); } return types.size(); } int CountNumber(int s[4]) { int empty = 0; for (int i(0); i < 4; i++) { empty += (s[i] < 0); } return 4 - empty; } int CountMax(int s[4]) { int max = 0; for (int i(0); i < 4; i++) { if (s[i] >= 0) { max = max > stamps[ s[i] ]? max : stamps[ s[i] ]; } } return max; } int CompareSolution(const void* a, const void* b) { SolutionAttributes *pa = (SolutionAttributes*)a; SolutionAttributes *pb = (SolutionAttributes*)b; if ((*pa).Types != (*pb).Types) { return (*pb).Types - (*pa).Types; } else if ((*pa).Number != (*pb).Number) { return (*pa).Number - (*pb).Number; } else { return (*pb).Max - (*pa).Max; } }