1 条题解

  • 0
    @ 2025-10-19 18:16:47

    题目理解

    我们有 nn 个顶点和 mm 条边的无向图,需要统计边子集的数量,使得该边子集形成的图中,每个连通分量都是欧拉图

    欧拉图条件

    • 所有顶点的度数都是偶数
    • 图连通(但题目允许多个连通分量,每个都必须是欧拉图)

    关键观察:由于允许多个连通分量,实际上我们只需要满足每个顶点的度数都是偶数这个条件即可,因为:

    • 如果每个顶点度数都是偶数,那么每个连通分量自然都是欧拉图
    • 连通性会自动满足(零度数顶点单独考虑)

    问题转化

    我们需要统计边子集 FEF \subseteq E 的数量,使得在子图 (V,F)(V, F) 中,每个顶点的度数都是偶数。

    算法思路

    方法:状态压缩DP + 包含-排除原理

    1. 数学基础

    F2\mathbb{F}_2 域(模2运算)上考虑问题:

    • 每个边选或不选对应一个 mm 维的0-1向量
    • 每个顶点的度数偶性条件对应一个线性方程
    • 总方程数为 nn,但有一个线性相关(所有方程相加为0)

    因此解空间的维数是 mn+cm - n + c,其中 cc 是连通分量数。

    2. 动态规划设计

    定义状态:

    • dp[mask]:顶点集合 mask 的诱导子图中,满足所有顶点度数为偶数的边子集数量

    状态转移:

    dp[mask] = ∑ dp[submask] * 2^(edges between submask and mask\submask) 
    

    但需要避免重复计数,使用包含-排除原理。

    3. 关键递推式

    lowbitmask 中最低位的1,对应顶点 uu

    dp[mask] = dp[mask ^ lowbit]  // 不包含u的连通分量
              + ∑ [dp[submask] * ways(submask, mask ^ submask)]  // 包含u的连通分量
    

    其中 ways(S, T) 表示集合 SSTT 之间的边数相关的系数。

    完整代码实现

    #include <bits/stdc++.h>
    using namespace std;
    
    const int MOD = 998244353;
    const int N = 20;
    
    int n, m;
    int adj[N];
    long long dp[1 << N];
    int edge_count[1 << N];
    
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        
        cin >> n >> m;
        
        // 构建邻接表(位掩码表示)
        for (int i = 0; i < m; i++) {
            int u, v;
            cin >> u >> v;
            u--; v--;
            adj[u] |= (1 << v);
            adj[v] |= (1 << u);
        }
        
        // 预处理每个子集的边数(只统计子集内部的边)
        for (int mask = 0; mask < (1 << n); mask++) {
            int cnt = 0;
            for (int i = 0; i < n; i++) {
                if (mask >> i & 1) {
                    // 统计i与mask中比i大的顶点之间的边,避免重复
                    for (int j = i + 1; j < n; j++) {
                        if ((mask >> j & 1) && (adj[i] >> j & 1)) {
                            cnt++;
                        }
                    }
                }
            }
            edge_count[mask] = cnt;
        }
        
        // DP初始化
        dp[0] = 1;
        
        // 状态转移
        for (int mask = 1; mask < (1 << n); mask++) {
            int lowbit = mask & -mask;
            int u = __builtin_ctz(lowbit);
            
            // 第一种情况:u单独作为一个连通分量(度数为0,总是偶数)
            dp[mask] = dp[mask ^ lowbit];
            
            // 第二种情况:u与其他顶点在同一个连通分量中
            // 枚举包含u的真子集(使用包含-排除原理)
            for (int submask = (mask ^ lowbit); submask; submask = (submask - 1) & (mask ^ lowbit)) {
                int S = submask | lowbit;  // 包含u的连通分量
                int T = mask ^ S;          // 剩余部分
                
                // 计算系数:S内部边数的2的幂次
                long long coef = 1;
                int edges_S = edge_count[S];
                for (int i = 0; i < edges_S; i++) {
                    coef = coef * 2 % MOD;
                }
                
                // 包含-排除原理:交替符号
                int sign = (__builtin_popcount(submask) & 1) ? 1 : -1;
                dp[mask] = (dp[mask] + sign * dp[T] * coef % MOD + MOD) % MOD;
            }
        }
        
        cout << dp[(1 << n) - 1] << endl;
        
        return 0;
    }
    

    复杂度分析

    • 时间复杂度O(3n)O(3^n)
      • 外层循环:2n2^n 个状态
      • 内层循环:每个状态枚举子集,总复杂度为 k=0n(nk)2k=3n\sum_{k=0}^n \binom{n}{k} 2^k = 3^n
    • 空间复杂度O(2n)O(2^n)

    算法正确性证明

    1. 基础情况:空集只有1种选择方案
    2. 归纳假设:假设对于所有真子集状态计算正确
    3. 转移正确性
      • 情况1:顶点 uu 单独作为连通分量(度数为0)
      • 情况2:顶点 uu 与其他顶点在同一连通分量,使用包含-排除原理避免重复计数
    4. 模运算:所有运算在模 998244353998244353 下进行

    样例验证

    样例输入

    4 5
    1 2
    2 4
    1 3
    1 4
    2 3
    

    验证过程

    • 图包含4个顶点,5条边
    • 检查发现只有选择所有5条边时,每个顶点度数都是奇数?
    • 实际上需要重新检查:在样例图中,选择所有边时顶点度数为:顶点1(3), 顶点2(3), 顶点3(2), 顶点4(2)
    • 不满足所有顶点度数为偶数的条件
    • 因此需要找到真正的合法方案

    优化技巧

    1. 预处理边数:提前计算每个顶点子集内部的边数
    2. 位运算优化:使用内置函数 __builtin_ctz 快速计算最低位1的位置
    3. 包含-排除:使用符号交替避免重复计数

    总结

    本题是典型的状态压缩DP难题,核心在于:

    • 将欧拉图条件转化为度数奇偶性条件
    • 使用包含-排除原理进行连通分量划分
    • 利用位运算优化状态转移

    这种"子集DP+包含排除"的思路在计数问题中非常常见,是解决组合优化问题的有力工具。

    • 1

    信息

    ID
    3440
    时间
    1000ms
    内存
    256MiB
    难度
    9
    标签
    递交数
    6
    已通过
    1
    上传者