818 - Cutting Chains (枚举子集 + 状态压缩)

题目链接


https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=759

题目大意


给定n个环,其中有些环可以扣在一起,一个环可以和多个环扣在一起。
现在需要求最少打开多少个环才能使这些环构成一条链。(当然打开了环还需要扣上,打开扣上算一次操作)

解题过程


既然是暴力里面的题,自然应该是暴力解决的。然后思路是枚举每一个环,枚举他的所有删边增边情况。大体用 IDA* 搞一搞。
然后超时了,跑到第四层就要好长时间,第五层直接走不动了。
然后就去翻了翻博客,原来是位运算枚举子集。

题目分析


  • 用集合表示每一个环打不打开的情况,首先所有的环至多打开一次。
  • 然后用二进制枚举枚举子集。
  • 统计下每个环的度,如果度大于 2,那么一定构不成链。
  • dfs 一下删除打开的环后,剩下的连通块有没有构成环。
  • 最后以打开的环当做边,看看能否把这些独立的链连起来。如果打开的环数大于等于独立的链的条数减一,即可连起来。

__builtin_popcount() 函数使用

int n;
__builtin_popcount(n);

函数返回 n 的二进制表示中 1 的个数。
用二进制枚举子集的时候可以用来统计 1 的个数。

AC代码


#include<bits/stdc++.h>
using namespace std;

int g[16][16], gmask[16];
int visited[16];

int dfs(int u, int p, int open, int n) {
    visited[u] = 1;
    for (int i = 0; i < n; i++) {
        if ((open>>i)&1)
            continue;
        if (g[u][i] == 0 || i == p)
            continue;
        if (visited[i] || dfs(i, u, open, n))
            return 1;
    }
    return 0;
}

int checkChain(int open, int n) {
    for (int i = 0; i < n; i++) {
        if ((open>>i)&1)
            continue;
        int t = gmask[i]^(gmask[i]&open);
        int degree = __builtin_popcount(t);
        if (degree > 2)
            return 0;
    }

    int op = __builtin_popcount(open);
    int comp = 0;
    memset(visited, 0, sizeof(visited));
    for (int i = 0; i < n; i++) {
        if ((open>>i)&1)
            continue;
        if (visited[i])
            continue;
        if (dfs(i, -1, open, n))
            return 0;
        comp++;
    }
    return op >= comp - 1;
}

int main() {
    int n, cases = 0;
    while (~scanf("%d", &n) && n) {
        memset(g, 0, sizeof(g));
        memset(gmask, 0, sizeof(gmask));
        while (true) {
            int a, b;
            scanf("%d %d", &a, &b);
            if (a == -1 && b == -1)
                break;
            a--, b--;
            g[a][b] = g[b][a] = 1;
            gmask[a] |= 1<<b, gmask[b] |= 1<<a;
        }

        int ret = 0x3f3f3f3f;
        for (int i = 0; i < (1<<n); i++) {
            int op = __builtin_popcount(i);
            if (op >= ret)
                continue;
            if (checkChain(i, n))
                ret = min(ret, op);
        }
        printf("Set %d: Minimum links to open is %d\n", ++cases, ret);
    }
}

转载于:https://www.cnblogs.com/ACMFish/p/7222856.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值