• DYOJ 【20220303模拟赛】最少分组 题解


    最少分组

    题意

    \(n\) 个点 \(m\) 条边的无向图,可以删掉 0 条或多条边,求满足条件的最小连通块数量:

    • 对每个顶点对 \((a,b)\) ,若 \(a\)\(b\) 同属于一个连通块,则 \(a,b\) 之间有边

    \(n\le 18\)

    题解

    显然状压

    \(f[V]\) 表示点集为 \(V\) 时的答案,则

    \[f[V]=\min f[V']+f[V-V'] \]

    其中 \(V'\)\(V\) 的子集

    初始化:\(f[S]=1\) 当且仅当 \(S\) 是原图的完全字图

    因为需要枚举 子集的子集,而一个点只有可能:

    • 不在第一层子集中
    • 在第一个子集但不在第二个子集
    • 在第二个子集

    一个点 3 种情况,复杂度 \(O(3^n)\)

    因为常数小,即使 \(3^{18}\ge3\times10^9\) 但也能过

    Code

    这里有新的姿势:如何枚举子集的子集?

    for (int i = 1; i < (1 << n); i++)
        for (int j = i; j; j = (j - 1) & i)
            ; // do something
    

    其中 (j - 1) & i 可以保证合法

    具体实现:

    #include <bits/stdc++.h>
    using namespace std;
    typedef unsigned long long uLL;
    typedef long double LD;
    typedef long long LL;
    typedef double db;
    const int N = 20;
    int n, m, g[N][N], mx, ans, f[1 << N];
    int main() {
        scanf("%d%d", &n, &m);
        mx = (1 << n) - 1;
        for (int i = 1, u, v; i <= m; i++) {
            scanf("%d%d", &u, &v), --u, --v;
            g[u][v] = g[v][u] = 1;
        }
        memset(f, 0x3f, sizeof(f));
        f[0] = 0;
        for (int i = 1, fl; i <= mx; i++) {
            fl = 1;
            for (int j = 0; j < n; j++) if ((i >> j) & 1)
                for (int k = j + 1; k < n; k++) if ((i >> k) & 1)
                    if (!g[j][k]) { fl = 0; break; }
            if (fl) f[i] = 1;
        }
        for (int i = 1; i <= mx; i++)
            for (int j = i; j; j = (j - 1) & i)
                f[i] = min(f[i], f[j] + f[j ^ i]);
        printf("%d", f[mx]);
    }
    

    总结

    状压中枚举子集的子集的技巧

  • 相关阅读:
    SAX解析xml,小实例
    Pull解析xml,小实例
    TCP通信小实例
    android 获取手机信息
    mysql创建用户与授权
    java执行SQL脚本文件
    IOUtils.readFully()的使用
    下载工具类
    vue element ui 父组件控制子组件dialog的显隐
    springboot-mybatis配置多数据源
  • 原文地址:https://www.cnblogs.com/KonjakLAF/p/16035663.html
Copyright © 2020-2023  润新知