有12道题目,每道题目需要分配A、B、C、D四种答案之一,且每种答案的数量有限制(na, nb, nc, nd)。同时,存在一些约束条件,指定某些题目必须答案相同。
解题思路
代码使用并查集(Union-Find)来分组必须答案相同的题目,然后使用回溯法为每个组分配答案类型,确保不超过数量限制,并统计有效方案数。
const rl = require("readline").createInterface({ input: process.stdin });
var iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
void (async function () {
let params = [];
while ((line = await readline())) {
let tokens = line.split(" ").map(Number); // 直接转为数字
params.push(tokens);
}
// 解析输入:第一行为na, nb, nc, nd, m
const [na, nb, nc, nd, m] = params[0];
// 存储答案数量限制(A-D对应索引0-3)
const limits = [na, nb, nc, nd];
// 存储m对约束(题目编号从1开始,转为0索引)
const constraints = [];
for (let i = 1; i <= m; i++) {
const [x, y] = params[i];
constraints.push([x - 1, y - 1]); // 转为0-based索引
}
// 并查集:用于合并必须答案相同的题目,同一组的题目答案必须一致
class UnionFind {
constructor(size) {
this.parent = Array.from({ length: size }, (_, i) => i);
}
find(x) {
if (this.parent[x] !== x) {
this.parent[x] = this.find(this.parent[x]); // 路径压缩
}
return this.parent[x];
}
union(x, y) {
const rootX = this.find(x);
const rootY = this.find(y);
if (rootX !== rootY) {
this.parent[rootY] = rootX; // 合并
}
}
}
// 初始化并查集,合并所有约束的题目
const uf = new UnionFind(12);
for (const [x, y] of constraints) {
uf.union(x, y);
}
// 统计每个根节点对应的题目组(同一组必须答案相同)
const groups = new Map();
for (let i = 0; i < 12; i++) {
const root = uf.find(i);
if (!groups.has(root)) {
groups.set(root, []);
}
groups.get(root).push(i);
}
const groupList = Array.from(groups.values()); // 所有题目组
const k = groupList.length; // 组的数量(需要独立选择答案的数量)
let count = 0; // 总方案数
// 回溯函数:为每个组选择答案,确保不超过数量限制
// index: 当前处理的组索引,counts: 已使用的A-D数量[A,B,C,D]
function backtrack(index, counts) {
if (index === k) {
// 所有组都已选择答案,且未超过限制,方案有效
count++;
return;
}
// 当前组的大小(需要消耗的答案数量)
const groupSize = groupList[index].length;
// 尝试为当前组选择A-D中的一种
for (let ans = 0; ans < 4; ans++) {
// 检查选择当前答案后是否超过限制
if (counts[ans] + groupSize <= limits[ans]) {
// 复制当前计数并更新
const newCounts = [...counts];
newCounts[ans] += groupSize;
// 处理下一个组
backtrack(index + 1, newCounts);
}
}
}
// 初始计数为[0,0,0,0](A-D均未使用),从第0个组开始处理
backtrack(0, [0, 0, 0, 0]);
console.log(count);
})();