1 条题解

  • 0
    @ 2025-11-4 16:05:23

    问题分析

    我们需要计算对于每个空地格子,当它变成障碍物后,在剩余网格中运输机投放方案的数量。这是一个网格图匹配计数问题。

    问题转化

    • 每个运输机投放对应一条边(连接两个相邻空地)
    • 每个格子最多被一条边覆盖
    • 这等价于在网格图中计算边覆盖方案数

    算法思路

    核心思想:状态压缩DP + 快速沃尔什变换(FWT)

    使用轮廓线动态规划结合集合卷积来高效计算所有可能的障碍物情况。

    主要步骤

    1. 预处理合法匹配模式

    void dfs(int x, int cur) {
        if (x == m) {
            stk[tp++] = cur;  // 存储当前行的合法匹配模式
            return;
        }
        // 水平匹配
        if (x && (~cur >> (x - 1) & 1))
            dfs(x + 1, cur | (1 << x) | (1 << (x - 1)));
        // 不匹配当前位置
        dfs(x + 1, cur);
    }
    

    2. 自底向上的DP计算

    从最后一行开始向上计算,f[i][mask] 表示从第 i 行开始,当前行状态为 mask 的方案数:

    for (int i = n - 1; ~i; --i) {
        for (int t = 0; t < tp; ++t) {  // 遍历所有合法匹配模式
            if (a[i] & stk[t]) continue;  // 跳过与障碍物冲突的模式
            int s = bm ^ (a[i] | stk[t]);  // 可用的列集合
            
            if (i + 1 < n) {
                // 与下一行的状态进行组合
                for (int ss = s;; ss = (ss - 1) & s) {
                    inc(f[i][ss | (bm ^ s)], f[i + 1][bm ^ ss]);
                    if (!ss) break;
                }
            } else {
                f[i][bm ^ s] = 1;  // 最后一行基础情况
            }
        }
        if (i) FWT(f[i]);  // 使用FWT加速状态转移
    }
    

    3. 计算每个格子作为障碍物的影响

    对于每个格子 (i,j),计算它变成障碍物后的方案数:

    // 初始化为总方案数
    for (int i = 0; i < n; ++i)
        for (int j = 0; j < m; ++j)
            ans[i][j] = sum;
    
    // 减去包含该格子的方案数
    for (int i = 0; i < (1 << m); ++i)
        for (int t = 0; t < m; ++t)
            if (i >> t & 1)
                dec(ans[0][t], f[0][i]);
    

    4. 使用FWT进行高效卷积

    void FWT(int *arr) {
        for (int i = 1; i < (1 << m); i <<= 1)
            for (int j = 0; j < (1 << m); j += (i << 1))
                for (int k = j; k < (j | i); ++k)
                    inc(arr[k | i], arr[k]);
    }
    

    算法原理

    状态表示

    • f[i][mask]: 从第 i 行开始,当前行状态为 mask 的方案数
    • mask 的二进制位表示对应列是否被当前行的匹配所覆盖

    状态转移

    通过快速沃尔什变换(FWT)将状态转移从 O(4m)O(4^m) 优化到 O(m2m)O(m \cdot 2^m)

    • FWT:将状态转换到"频域",使卷积变为点乘
    • 卷积计算:在频域中进行高效的状态组合
    • IFWT:将结果转换回原始域

    容斥原理

    对于每个格子 (i,j)

    答案 = 总方案数 - 包含该格子的方案数
    

    复杂度分析

    时间复杂度

    • 预处理O(2m)O(2^m)(生成合法匹配模式)
    • DP计算O(nm3m)O(n \cdot m \cdot 3^m)(使用FWT优化后)
    • 答案计算O(nm2m)O(n \cdot m \cdot 2^m)
    • 总复杂度:在 n,m17n, m \leq 17 时可行

    空间复杂度

    • O(n2m)O(n \cdot 2^m):存储DP数组
    • O(2m)O(2^m):FWT相关数组

    关键优化

    1. FWT加速

    将状态转移中的子集卷积转化为点乘,大幅降低复杂度。

    2. 滚动数组

    在计算过程中复用数组空间,减少内存使用。

    3. 位运算优化

    使用内置函数 __builtin_popcount 快速计算二进制中1的个数。

    4. 增量计算

    对于相邻的查询,复用大部分计算结果。

    算法正确性

    边界情况处理

    1. 最后一行:基础情况正确初始化
    2. 障碍物冲突:跳过与障碍物冲突的匹配模式
    3. 边界条件:正确处理网格边界

    数学严谨性

    • 使用模运算避免溢出
    • FWT变换保证卷积计算的正确性
    • 容斥原理确保计数不重不漏

    总结

    本题通过结合状态压缩DP和快速沃尔什变换,高效解决了网格图匹配计数问题。算法的核心优势在于:

    1. 理论创新:将匹配计数问题转化为集合卷积问题
    2. 工程优化:使用FWT大幅降低计算复杂度
    3. 实现精巧:通过位运算和状态压缩高效处理状态转移
    4. 通用性强:框架可扩展到其他网格图计数问题

    该解法在理论深度和工程实现上都达到了很高水平,体现了算法竞赛中数学与计算机科学的完美结合。

    • 1

    「CodePlus 2018 3 月赛」白金元首与莫斯科

    信息

    ID
    4974
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    8
    已通过
    1
    上传者