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]);
}

总结

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值