UVA11825 Hackers' Crackdown

状压DP

首先感谢lrj的透彻讲解.

我们要使一项服务瘫痪,就必须选择一些计算机,使它们与他们所相连的计算机是所有的计算机,即:我们将每一个计算机本身及其相连的计算机看成一个集合\(P_i\),我们要分成尽量多的集合,使每一个集合里\(Pi∪...∪Pj\)为全集。

我们又发现n的值比较小,因此我们可以考虑一下状压,\(P_i\)的每一位表示\(i\)号计算机的联通情况,\(C_i\)(这是数组,不是组合数)表示我们选择该二进制位下所选择的计算机的并集,\(f(C_i)\)表示在二进制下\(C_i\)可以分成的最大答案。

那么我们就要枚举全集子集的子集,对于子集,我们循环就行了,对于子集的子集,我们这样写就行了(虽然我也不知道为什么)学长说记住就行了

for(int S=0;S<=maxn;++S)//全集的子集 
for(int s0=S;s0;s0=(s0-1)&S)//全集的子集的子集

关于这样的时间复杂度,我们知道一个大小为\(n\) 的集合的子集的个数是\(2^n=\sum_{k=0}^{n}C_{n}^{k}\)(这里是组合数),那么一个集合的子集的子集的个数就是\(\sum_{k=0}^{n} C_{n}^{k}2^k\),根据二项式定理\((a+b)^k=\sum_{k=0}^{n}C_n^k a^kb^{n-k}\),我们逆用后就能得到原式为\((2+1)^n=3^n\),所以时间复杂度为\(O(3^n)\)O(能过)

剩下的就是一个简单的DP啦

另外用位运算也可以优化一下常数。

最后献上我丑陋的代码。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int n,m,x,maxn,my_case;
int p[21],c[1<<18],f[1<<18];
int main()
{
    while(scanf("%d",&n)&&n)
    {
        maxn=(1<<n)-1;
        memset(c,0,sizeof(c));memset(f,0,sizeof(f));
        for(int i=0;i<n;++i)
        {
            p[i]=(1<<i);scanf("%d",&m);
            while(m--){scanf("%d",&x);p[i]|=(1<<x);}
        }
        
        for(int S=0;S<=maxn;++S)
            for(int i=0;i<n;++i) if(S&(1<<i))c[S]|=p[i];
        
        for(int S=0;S<=maxn;++S)//全集的子集 
            for(int s0=S;s0;s0=(s0-1)&S)//全集的子集的子集 
            if(c[s0]==maxn)f[S]=max(f[S],f[S-s0]+1);
        
        printf("Case %d: %d\n",++my_case,f[maxn]);
    }
    return 0;
}

转载于:https://www.cnblogs.com/wljss/p/11508791.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值