四个选项

18 阅读2分钟

有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);
})();