NKOJ3720 黑客攻击 [状态压缩][背包DP]
问题描述
假设你是一个黑客,侵入了一个有着n台计算机(编号0,1,…,n-1)的网络。一共有n种服务,每台计算机都运行着所有服务。对于每台计算机,你都可以选择一项服务,终止这台计算机和所有与它相邻计算机的该项服务(如果其中一些服务已经停止,则这些服务继续处于停止状态)。你的目标是让尽量多的服务完全瘫痪(即:没有任何计算机运行该项服务)。
输入格式
输入包含多组数据。
每组数据的第一行为整数n(1<=n<=161<=n<=16):以下n行每行描述一台计算机的相邻计算机,其中第一个数m为相邻计算机的个数,接下来m个整数为这些计算机的编号。
输入结束的标志是n=0。
输出格式
对于每组数据,输出完全瘫痪的服务器的最大数量
样例输入
3
2 1 2
2 0 2
2 0 1
4
1 1
1 0
1 3
1 2
0
样例输出
Case 1: 3
Case 2: 2
解法
范围这么小,当然是考虑状压或者搜索了。这里又密切和每台电脑的状态相关联,基本上就是状压了。
首先要把每台电脑的关系给压出来。比如样例Case 2中(11)(11)表示0和1号电脑相连。几台电脑相连意味着其中的任意一台电脑被停止,那么与它相连的所有电脑的任意一项服务都会被停止。
题目要求完全瘫痪,因此我们要想办法凑出(11…1)(n个1)的状态。任选几台电脑,只要与它们相连的电脑构成的集合(并集)覆盖了所有服务器,那么攻击这些电脑就可以使得服务器完全瘫痪。
设定状态f[s]f[s]表示攻击集合ss中的电脑最多能够使得多少服务器完全瘫痪。这时只要找出尽可能多的子集(每个子集的攻击范围都能覆盖所有服务器)即可。这表示为方程就是:f[s]=max{f[s^ss]}+1 (ss是s的子集 且 ss的攻击范围覆盖了所有服务器)
。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int A[20],B[66000],f[66000];
//A[i]表示第i台电脑与哪些电脑相连
//B[i]用来表示攻击s集合中的能够使得哪些服务器瘫痪
int main(){
int n,cnt=0;scanf("%d",&n);
while(n){
cnt++;
for(int i=0;i<n;i++){
int t,tt=0;scanf("%d",&t);
tt|=(1<<i);
for(int j=1;j<=t;j++){
int ttt;scanf("%d",&ttt);
tt|=(1<<ttt);
}
A[i+1]=tt;
}
int nn=(1<<n)-1;
for(int s=1;s<=nn;s++)
for(int i=0;i<n;i++)
if(s&(1<<i))B[s]|=A[i+1];
for(int i=1;i<=nn;i++)
for(int j=i;j;j=(j-1)&i)
if(B[j]==nn)f[i]=max(f[i],f[i^j]+1);
printf("Case %d: %d\n",cnt,f[nn]);
memset(f,0,sizeof(f));
memset(A,0,sizeof(A));
memset(B,0,sizeof(B));
scanf("%d",&n);
}
return 0;
}