T4 自动兑换机
metaphysis · · 题解
题目链接
本题是纸币找零问题,与完全背包问题有相似之处。转换为美分后,需要兑换的纸币数量对应背包的容量,不同种类的硬币对应着不同的物品,硬币的面值对应着物品的价值,不过与标准的完全背包问题不同的是,此处所求的并不是背包能够达到的最大价值,而是求恰能够将背包装满时所使用的最少物品数量即具体的物品数量构成。
最少找零问题可以通过动态规划予以解决。给定一组面值
很显然,在初始化的时候,找
由于需输出最少硬币方案的具体构成,因此在自底向上进行动态规划时需要记录每一次选择时的相关参数,以便重建选择路径时使用。
以下是暂不考虑方案字典序最小时的解题方案。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n;
int denom[110];
int coins[10010], parent[10010], idx[10010], cnt[110];
void findPath(int money)
{
if (money > 0)
{
cnt[idx[money]]++;
findPath(parent[money]);
}
}
void findMiniumCoins(int money)
{
fill(coins, coins + 10010, INF);
fill(cnt, cnt + 110, 0);
coins[0] = 0;
for (int m = 1; m <= money; m++)
{
int minCoins = INF, minIdx = INF;
for (int d = 0; d < n; d++)
if (m >= denom[d] && coins[m - denom[d]] != INF &&
minCoins > (coins[m - denom[d]] + 1))
minCoins = coins[m - denom[d]] + 1, minIdx = d;
if (minIdx != INF)
{
coins[m] = minCoins;
parent[m] = m - denom[minIdx];
idx[m] = minIdx;
}
}
if (coins[money] == INF)
cout << "No solution." << endl;
else
{
cout << coins[money];
findPath(money);
int plusPrinted = 0;
for (int i = 0; i < n; i++)
if (cnt[i] > 0)
{
cout << (plusPrinted++ ? "+" : " ");
cout << denom[i] << "*" << cnt[i];
}
cout << endl;
}
}
int main(int argc, char *argv[])
{
double money;
int cases;
cin >> cases;
for (int cs = 1; cs <= cases; cs++)
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> denom[i];
sort(denom, denom + n);
n = unique(denom, denom + n) - denom;
cin >> money;
findMiniumCoins((int)(money * 100.0 + 0.5));
}
return 0;
}
可以证明:对给定的面值按递增排序,去除重复值,如果满足
但是对于不满足上述性质的硬币面值序列,可能存在多种的兑换方案,使用上述代码得到的可能并不是正确答案。此时可以考虑对面值按照字典序进行排列,先使用字典序小的面值进行兑换,得到以下代码。
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n;
int denom[110];
int coins[10010], parent[10010], idx[10010], cnt[110];
void findPath(int money)
{
if (money > 0)
{
cnt[idx[money]]++;
findPath(parent[money]);
}
}
void findMiniumCoins(int money)
{
fill(coins, coins + 10010, INF);
fill(cnt, cnt + 110, 0);
coins[0] = 0;
for (int m = 1; m <= money; m++)
{
int minCoins = INF, minIdx = INF;
for (int d = 0; d < n; d++)
if (m >= denom[d] && coins[m - denom[d]] != INF &&
minCoins > (coins[m - denom[d]] + 1))
minCoins = coins[m - denom[d]] + 1, minIdx = d;
if (minIdx != INF)
{
coins[m] = minCoins;
parent[m] = m - denom[minIdx];
idx[m] = minIdx;
}
}
if (coins[money] == INF)
cout << "No solution." << endl;
else
{
cout << coins[money];
findPath(money);
map<int, int> output;
for (int i = 0; i < n; i++)
if (cnt[i])
output[denom[i]] = cnt[i];
int plusPrinted = 0;
for (auto p : output)
{
cout << (plusPrinted++ ? "+" : " ");
cout << p.first << "*" << p.second;
}
cout << endl;
}
}
int cmp(int a, int b)
{
return to_string(a) < to_string(b);
}
int main(int argc, char *argv[])
{
double money;
int cases;
cin >> cases;
for (int cs = 1; cs <= cases; cs++)
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> denom[i];
sort(denom, denom + n, cmp);
n = unique(denom, denom + n) - denom;
cin >> money;
findMiniumCoins((int)(money * 100.0 + 0.5));
}
return 0;
}
对于上述使用简单排序后再进行动态规划的方法,虽然考虑到了面值字典序大小对方案字典序大小的影响,但是未考虑到硬币个数、* 符号、+ 符号对方案字典序大小的影响(测试点
1
11 304 399 203 268 173 76 39 241 266 18 393 7.63
使用前述代码的输出为:
4 18*1+173*2+399*1
但正确的输出为:
4 18*1+173*1+268*1+304*1
基于以上原因,必须在动态规划过程中记录每种具有最小硬币数量的兑换方案,最后使用回溯来搜索具有最小字典序的兑换方案。以下是一种使用
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n, denom[110], coins[10010];
vector<int> parent[10010];
map<int, int> path, best;
void dfs(int money)
{
if (money > 0)
{
if (best.size())
{
auto it1 = path.begin(), it2 = best.begin();
for (int i = 0; i < path.size() - 1; i++)
{
string s1 = to_string(it1->first) + '*' + to_string(it1->second);
string s2 = to_string(it2->first) + '*' + to_string(it2->second);
if (s1 != s2)
{
if (s1 > s2) return;
break;
}
it1++, it2++;
}
}
for (auto m : parent[money])
{
if (path.size() && m < path.rbegin()->first) continue;
path[m]++;
dfs(money - m);
path[m]--;
if (!path[m]) path.erase(m);
}
}
else
{
if (best.size())
{
auto it1 = path.begin(), it2 = best.begin();
for (int i = 0; i < path.size(); i++)
{
string s1 = to_string(it1->first) + '*' + to_string(it1->second);
string s2 = to_string(it2->first) + '*' + to_string(it2->second);
if (s1 != s2)
{
if (s1 > s2) return;
break;
}
it1++, it2++;
}
}
best = path;
}
}
void dp(int money)
{
for (int i = 0; i <= money; i++) parent[i].clear();
coins[0] = 0;
for (int i = 1; i <= money; i++)
{
coins[i] = INF;
for (int j = 0; j < n; j++)
if (i >= denom[j] && coins[i - denom[j]] != INF)
{
if (coins[i] > coins[i - denom[j]] + 1)
{
coins[i] = coins[i - denom[j]] + 1;
parent[i].clear();
parent[i].push_back(denom[j]);
}
else if (coins[i] == coins[i - denom[j]] + 1)
{
parent[i].push_back(denom[j]);
}
}
}
if (coins[money] == INF) cout << "No solution." << endl;
else
{
path.clear();
best.clear();
dfs(money);
cout << coins[money] << ' ';
bool plus = false;
for (auto p : best)
{
if (plus) cout << '+';
cout << p.first << '*' << p.second;
plus = true;
}
cout << '\n';
}
}
int main(int argc, char *argv[])
{
int cases;
cin >> cases;
for (int cs = 1; cs <= cases; cs++)
{
cin >> n;
for (int i = 0; i < n; i++) cin >> denom[i];
sort(denom, denom + n);
n = unique(denom, denom + n) - denom;
double money;
cin >> money;
dp((int)(money * 100.0 + 0.5));
}
return 0;
}
但是由于上述代码使用
附注:基于 Macesuted 的建议,卡
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int n, denom[110], coins[10010];
int parent[10010][110], parentCnt[10010];
int path[10010], best[10010];
int pathCnt[10010], bestCnt[10010];
int pathTotal, bestTotal;
void dfs(int money)
{
if (money > 0)
{
if (bestTotal)
{
for (int i = 0; i < pathTotal - 1; i++)
{
if (path[i] != best[i])
{
if (to_string(path[i]) > to_string(best[i])) return;
break;
}
if (pathCnt[i] != bestCnt[i])
{
if (to_string(pathCnt[i]) > to_string(bestCnt[i])) return;
break;
}
}
}
for (int i = 0; i < parentCnt[money]; i++)
{
int m = parent[money][i];
if (pathTotal && m < path[pathTotal - 1]) continue;
if (m == path[pathTotal -1]) pathCnt[pathTotal - 1]++;
else path[pathTotal] = m, pathCnt[pathTotal]++, pathTotal++;
dfs(money - m);
pathCnt[pathTotal - 1]--;
if (pathCnt[pathTotal - 1] == 0)
pathTotal--;
}
}
else
{
if (bestTotal)
{
for (int i = 0; i < pathTotal; i++)
{
if (path[i] != best[i])
{
if (to_string(path[i]) > to_string(best[i])) return;
break;
}
if (pathCnt[i] != bestCnt[i])
{
if (to_string(pathCnt[i]) > to_string(bestCnt[i])) return;
break;
}
}
}
bestTotal = pathTotal;
for (int i = 0; i < pathTotal; i++)
best[i] = path[i], bestCnt[i] = pathCnt[i];
}
}
void dp(int money)
{
for (int i = 0; i <= money; i++) parentCnt[i] = 0;
coins[0] = 0;
for (int i = 1; i <= money; i++)
{
coins[i] = INF;
for (int j = 0; j < n; j++)
if (i >= denom[j] && coins[i - denom[j]] != INF)
{
if (coins[i] > coins[i - denom[j]] + 1)
{
coins[i] = coins[i - denom[j]] + 1;
parentCnt[i] = 0;
parent[i][parentCnt[i]++] = denom[j];
}
else if (coins[i] == coins[i - denom[j]] + 1)
{
parent[i][parentCnt[i]++] = denom[j];
}
}
}
if (coins[money] == INF) cout << "No solution." << endl;
else
{
bestTotal = pathTotal = 0;
memset(bestCnt, 0, sizeof bestCnt);
memset(pathCnt, 0, sizeof pathCnt);
dfs(money);
cout << coins[money] << ' ';
bool plus = false;
for (int i = 0; i < bestTotal; i++)
{
if (plus) cout << '+';
cout << best[i] << '*' << bestCnt[i];
plus = true;
}
cout << '\n';
}
}
int main(int argc, char *argv[])
{
int cases;
cin >> cases;
for (int cs = 1; cs <= cases; cs++)
{
cin >> n;
for (int i = 0; i < n; i++) cin >> denom[i];
sort(denom, denom + n);
n = unique(denom, denom + n) - denom;
double money;
cin >> money;
dp((int)(money * 100.0 + 0.5));
}
return 0;
}