UVA 11825 Hackers' Crackdown

题意:有一个工作网络,有n台计算机,总共有n个服务,先开始每个计算机都运行着这n项服务,现在有一个黑客他要入侵,他能停止每台计算机上的一项服务,那么和这台计算机相邻的的计算机的这项服务也会停止,如果一项服务在所有计算机中都停止了,那么这项服务就瘫痪了。问你最多他能瘫痪多少项服务。

思路:先开始这道题没想法,又是要全部停止才算瘫痪,又是网络的,后来一看数据范围,n是16,马上就想到状态压缩了,可是就感觉很麻烦,很难下手,模型抽象不出来。

看了一下书,也是瞬时顿悟了。其实这道题就是给你p0、p1、p2……pn-1这n个集合,问你最多能分多少组,使得每组的并集是全集。用d[ s ] 表示状态s最多能分多少组,那么d[ s ] = max( d[ s0 ] +1,s0为全集 )。所以在状态转移之前还要预处理一个cover数组,用来表示选的那几个集合的并集。这里枚举i的子集做状态转移的时候,还是有枚举方法的,千万不要j++这样枚举,具体见下面的代码:

for(int i = 1;i<=S;i++)
        {
            d[i]=0;
            
            for(int j = i;j;j = (j-1)&i)
                if(cover[j]==full)
                    d[i] = max(d[i],d[i^j]+1);
            
        }


这里的j枚举的不是子状态,而是要变成i所需要拼上去的那部分。如果j要直接枚举子状态,那么需要增加一个特判,即j=0,因为for里面的终止条件是j>0,也就是说漏了个j=0,如果改成j>=0,由于j的转换方程,那么会陷入无限循环。上述代码的时间复杂度是O(3^n)。

又想起下午多校那一道了,之前就做过的话,就不会TLE了。。 = =

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int MAXN = 1<<17;

int p[MAXN];

int n;

int cover[MAXN];

void get_cover()
{
    int S = (1<<n)-1;
    cover[0]=0;
    for(int s=1;s<=S;s++)
    {
        cover[s] = 0;
        for(int i = 0;i<n;i++)
        {
            if(s&(1<<i)) cover[s] |= p[i];
        }
    }
}

int d[MAXN];

int main()
{
    int cas = 0;
    while(scanf("%d",&n)&&n)
    {
        for(int i = 0;i<n;i++)
        {
            int m;
            scanf("%d",&m);
            int a;
            p[i] = (1<<i);
            while(m--)
            {
                scanf("%d",&a);
                p[i] |= (1<<a);
            }
        }
        get_cover();
        int S = (1<<n)-1;
        d[0]=0;
        int full = (1<<n)-1;
        for(int i = 1;i<=S;i++)
        {
            d[i]=0;
            
            for(int j = i;j;j = (j-1)&i)
                if(cover[j]==full)
                    d[i] = max(d[i],d[i^j]+1);
            
        }
        printf("Case %d: %d\n",++cas,d[S]);
    }
    return 0;
}


你可能感兴趣的:(UVA 11825 Hackers' Crackdown)