1 条题解
-
0
题目描述
Phil Kropotnik 是一名游戏制作人,需要为游戏设计非传统的骰子。给定一组已经确定的骰子,要求设计最后一个骰子的面值,使得所有骰子的组合能够满足特定的总和及其出现次数。具体来说:
- 输入包含多组数据,每组数据包括:
- 已指定的骰子数量
n
及其面值。 - 待确定骰子的面数
r
和m
个条件,每个条件包括一个目标总和v_i
和对应的组合数c_i
。
- 已指定的骰子数量
- 输出满足条件的骰子面值(非降序排列),如果无解则输出 "Impossible"。
- 骰子面值为 1 到 50 的整数,且需选择字典序最小的解。
解题思路
问题分析
- 组合数计算:对于给定的骰子组合,计算所有可能的和及其出现次数。
- 条件满足:设计的骰子必须满足所有给定的
(v_i, c_i)
条件。 - 搜索空间:待确定骰子的面值范围为 1 到 50,且面数为
r
(4 ≤ r ≤ 6),搜索空间较大,需优化。
关键步骤
-
预处理已知骰子的和组合:
- 使用动态规划计算已知骰子的所有可能和及其出现次数。
- 例如,
sum_counts[s]
表示已知骰子组合得到和s
的方式数。
-
计算待定骰子的贡献:
- 对于待定骰子的每个可能面值
x
,计算其对每个条件(v_i, c_i)
的贡献:- 需要满足
sum_counts[v_i - x]
的和等于c_i
。
- 需要满足
- 预处理
x_contrib[i][x] = sum_counts[v_i - x]
,表示面值x
对条件i
的贡献。
- 对于待定骰子的每个可能面值
-
回溯搜索:
- 按非降序生成待定骰子的面值组合(避免重复搜索)。
- 对于部分面值组合,检查是否能满足所有条件:
- 当前贡献 + 剩余面值的最大/最小可能贡献是否可能满足条件。
- 剪枝:如果当前部分面值已经无法满足条件,提前终止搜索。
-
选择最优解:
- 优先选择字典序最小的面值组合。
优化
- 字典序生成:按非降序生成面值组合,确保第一个找到的解即为字典序最小。
- 剪枝:利用预处理的最大/最小贡献信息,提前排除不可能的分支。
- 动态规划:高效计算已知骰子的和组合。
代码实现
#include <bits/stdc++.h> using namespace std; struct Condition { int v; int c; }; vector<int> parse_faces(const string& line) { vector<int> res; istringstream iss(line); int f; iss >> f; for (int i = 0; i < f; ++i) { int x; iss >> x; res.push_back(x); } return res; } bool read_input(int& n, vector<vector<int>>& dice, int& r, vector<Condition>& conds) { string line; if (!getline(cin, line)) return false; while (line.empty()) { if (!getline(cin, line)) return false; } istringstream iss(line); iss >> n; if (n == 0) return false; dice.clear(); for (int i = 0; i < n; ++i) { while (getline(cin, line)) { if (!line.empty()) break; } vector<int> faces = parse_faces(line); dice.push_back(faces); } while (getline(cin, line)) { if (!line.empty()) break; } istringstream iss2(line); int m; iss2 >> r >> m; conds.resize(m); for (int i = 0; i < m; ++i) { iss2 >> conds[i].v >> conds[i].c; } return true; } void preprocess_sum_counts(const vector<vector<int>>& dice, unordered_map<int, int>& sum_counts) { sum_counts.clear(); sum_counts[0] = 1; for (const auto& die : dice) { unordered_map<int, int> new_counts; for (const auto& [s, cnt] : sum_counts) { for (int face : die) { new_counts[s + face] += cnt; } } sum_counts.swap(new_counts); } } void preprocess_x_contrib(int m, const vector<Condition>& conds, const unordered_map<int, int>& sum_counts, vector<vector<int>>& x_contrib) { x_contrib.assign(m, vector<int>(51, 0)); for (int i = 0; i < m; ++i) { int v_i = conds[i].v; for (int x = 1; x <= 50; ++x) { int s = v_i - x; if (sum_counts.count(s)) { x_contrib[i][x] = sum_counts.at(s); } else { x_contrib[i][x] = 0; } } } } void preprocess_max_min_contrib(int m, const vector<Condition>& conds, const vector<vector<int>>& x_contrib, vector<vector<int>>& max_contrib, vector<vector<int>>& min_contrib) { max_contrib.assign(m, vector<int>(51, 0)); min_contrib.assign(m, vector<int>(51, 0)); for (int i = 0; i < m; ++i) { int current_max = 0; for (int x = 50; x >= 1; --x) { current_max = max(current_max, x_contrib[i][x]); max_contrib[i][x] = current_max; } int current_min = INT_MAX; for (int x = 50; x >= 1; --x) { current_min = min(current_min, x_contrib[i][x]); min_contrib[i][x] = current_min; } } } void backtrack(int pos, int last_val, vector<int>& current_faces, vector<int> current_c, const vector<Condition>& conds, int r, const vector<vector<int>>& x_contrib, const vector<vector<int>>& max_contrib, const vector<vector<int>>& min_contrib, vector<int>& best) { if (pos == r) { for (int i = 0; i < conds.size(); ++i) { if (current_c[i] != conds[i].c) { return; } } if (best.empty() || current_faces < best) { best = current_faces; } return; } for (int x = last_val; x <= 50; ++x) { vector<int> new_c = current_c; for (int i = 0; i < conds.size(); ++i) { new_c[i] += x_contrib[i][x]; } bool possible = true; int remaining = r - pos - 1; for (int i = 0; i < conds.size(); ++i) { int required = conds[i].c; int max_p = new_c[i] + max_contrib[i][x] * remaining; int min_p = new_c[i] + min_contrib[i][x] * remaining; if (max_p < required || min_p > required) { possible = false; break; } } if (possible) { current_faces.push_back(x); backtrack(pos + 1, x, current_faces, new_c, conds, r, x_contrib, max_contrib, min_contrib, best); current_faces.pop_back(); } } } void solve() { int n; vector<vector<int>> dice; int r; vector<Condition> conds; while (true) { if (!read_input(n, dice, r, conds)) { break; } unordered_map<int, int> sum_counts; preprocess_sum_counts(dice, sum_counts); vector<vector<int>> x_contrib; preprocess_x_contrib(conds.size(), conds, sum_counts, x_contrib); vector<vector<int>> max_contrib, min_contrib; preprocess_max_min_contrib(conds.size(), conds, x_contrib, max_contrib, min_contrib); vector<int> best; vector<int> current_faces; vector<int> current_c(conds.size(), 0); backtrack(0, 1, current_faces, current_c, conds, r, x_contrib, max_contrib, min_contrib, best); if (best.empty()) { cout << "Impossible" << endl; } else { cout << "Final die face values are"; for (int x : best) { cout << " " << x; } cout << endl; } } } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); solve(); return 0; }
示例解释
- 样例输入 1:
- 已知骰子:
[1, 10, 15, 20]
。 - 待定骰子面数:5,条件:
(2,3), (3,1), (11,3), (16,4), (26,1)
。 - 输出:
1 1 1 2 6
,因为这是满足所有条件的最小字典序解。
- 已知骰子:
- 样例输入 2:
- 无解,输出 "Impossible"。
- 样例输入 3:
- 输出
3 7 9 9
,满足所有条件的最小字典序解。****
- 输出
- 输入包含多组数据,每组数据包括:
- 1
信息
- ID
- 1038
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者