题意:假设你是一个黑客, 侵入了一个有着n台计算机(编号为0,1,…,n-1) 的网络。 一共有n种服务,每台计算机都运行着所有服务。对于每台计算机,你都可以选择一项服务, 终止这台计算机和所有与它相邻计算机的该项服务(如果其中一些服务已经停止, 则这些服务继续处于停止状态)。 你的目标是让尽量多的服务器完全瘫痪(即: 没有任何计算机运行该项服务)。
思路:这道题的话,要注意的地方是如果一个电脑被入侵了,那么它的相邻的电脑的相邻的电脑也会是被入侵了。
p[i]表示的与i直接相邻的一个状态,没太大直接意义,主要是为了推出c的状态,c[i]表示的是i状态被选择时,n个电脑被覆盖的状态。dp[i]表示i状态被选择,已经被入侵的最大服务数。用到枚举子集,假设集合i为j的一个子集,则(i-1)&j为j的下一个子集,直至i为0。其实就是一个状态能够达到全集,然后和另外一个状态合并成一个新的状态,然后就是dp取最大值。
AC代码:
#include <stdio.h>
#include <string.h>
#include <string>
#include <algorithm>
#include <iostream>
#include <math.h>
typedef long long ll;
const int maxx=20;
const int inf=0x3f3f3f3f;
using namespace std;
int p[maxx];
int c[1<<maxx];
int dp[1<<maxx];
int main()
{
int n;
int k=1;
while(~scanf("%d",&n),n)
{
int a,b;
for(int i=0; i<n; i++)
{
scanf("%d",&a);
p[i]=1<<i;//左移,构造一个只有第i+1位是1的二进制数,为了将0表示在第一位上,故这样操作
while(a--)
{
scanf("%d",&b);
p[i]|=(1<<b);//与i直接相连的电脑的表示
}
}
c[0]=0;
for(int i=1; i<=(1<<n); i++)
{
c[i]=0;//i表示枚举所有电脑组成的情况
for(int j=0; j<n; j++)
{
if(i&(1<<j))
c[i]|=p[j];//若此位上有电脑则将其所有相邻的电脑及本身加入此集合
}
}
int ans=(1<<n)-1;//只有第n+1位没有电脑,即所有n台都被感染
for(int i=1; i<(1<<n); i++)
{
dp[i]=0;
for(int j=i; j; j=(j-1)&i)//子集枚举,减少电脑数与原集合取交集
{
if(c[j]==ans)//每台电脑都运行着所有的服务,题意要求每台电脑可以选择关一项服务,如果c[j]==all表示如果关掉(j在二进制下的表示)的电脑上的某服务,必有一项服务在所有电脑上都被关掉
dp[i]=max(dp[i],dp[i^j]+1);
}
}
printf("Case %d: %d\n",k++,dp[ans]);
}
return 0;
}