题面
假设你是一个黑客, 侵入了一个有着n台计算机(编号为0,1,…,n-1) 的网络。 一共有n种服务, 每台计算机都运行着所有服务。 对于每台计算机, 你都可以选择一项服务, 终止这台计算机和所有与它相邻计算机的该项服务(如果其中一些服务已经停止, 则这些服务继续处于停止状态) 。 你的目标是让尽量多的服务器完全瘫痪(即: 没有任何计算机运行该项服务) (1≤n≤16)
分析
看这样例,真的是状压吗喵喵喵???咋看起来像把联通的环的个数求出来,然后每个环的节点数取min啊...
太蠢了,忽视上面的内心os。以前都是想得太少,这次是想太多了。样例不水还能叫样例吗??计算机之间是可能有传递的影响关系的啊..
我们把每个计算机和它相邻的计算机归入一个集合。
然后将这些集合分组后,使每一组集合并起来都等于全集(即让几组相邻的计算机合起来包含了所有计算机,这样就可以停止一个服务器)
所以用f[s]表示状态为s的最大破坏数量,s的二进制形式第k位的1和0表示第k台服务器有没有被选择
还需要预处理选择每个计算机能覆盖的计算机,用cover[i]存。比如四台计算机中,0和1相邻,1和2相邻,则cover[0]=0111
最后枚举子集,枚举的是选哪些计算机,只要这个子集能覆盖完所有的计算机,就合法。枚举子集的方式:s&(s-1)
代码
#include<bits/stdc++.h> using namespace std; #define N 18 int n,x,mx,cnt,cas; int neb[N],f[1<<N],cover[1<<N]; inline void init() { mx=(1<<n)-1;cas++; memset(f,0,sizeof(f)); memset(neb,0,sizeof(neb)); memset(cover,0,sizeof(cover)); } int main() { while(scanf("%d",&n)&&n) { init(); for(int i=0;i<n;i++) { scanf("%d",&cnt); neb[i]=1<<i; while(cnt--){scanf("%d",&x);neb[i]|=(1<<x);} } for(int i=0;i<=mx;i++) for(int j=0;j<n;j++) if(i&(1<<j)) cover[i]|=neb[j]; for(int s=1;s<=mx;s++) for(int s0=s;s0;s0=(s0-1)&s) if(cover[s0]==mx) f[s]=max(f[s],f[s^s0]+1); printf("Case %d: %d ",cas,f[mx]); } return 0; }