黑客的攻击(Hacker's Crackdown, UVa 11825)状压dp枚举子集

版权声明:本文为博主原创文章,未经博主允许不得转载,欢迎添加友链。 https://blog.csdn.net/qq_42835910/article/details/90082562

题目链接
假设你是一个黑客, 侵入了一个有着n台计算机(编号为0,1,…,n-1) 的网络。 一共有n种服务, 每台计算机都运行着所有服务。 对于每台计算机, 你都可以选择一项服务, 终止这台计算机和所有与它相邻计算机的该项服务(如果其中一些服务已经停止, 则这些服务继续处于停止状态) 。 你的目标是让尽量多的服务器完全瘫痪(即: 没有任何计算机运行该项服务) 。
【输入格式】
输入包含多组数据。 每组数据的第一行为整数n(1≤n≤16) ; 以下n行每行描述一台计算机的相邻计算机, 其中第一个数m为相邻计算机个数, 接下来的m个整数为这些计算机的编号。 输入结束标志为n=0。
【输出格式】
对于每组数据, 输出完全瘫痪的服务的最大数量。
【分析】
本题的数学模型是: 把n个集合P1,P2,…,Pn分成尽量多组, 使得每组中所有集合的并集等于全集。 这里的集合Pi就是计算机i及其相邻计算机的集合, 每组对应于题目中的一项服务。 注意到n很小, 可以用《算法竞赛入门经典》 中提到的二进制法表示这些集合, 即在代码中, 每个集合Pi实际上是一个非负整数。 

状压枚举子集:状压枚举子集就相当于在原集合的二进制状态下把一些1换为0,而我们每次−1然后进行与运算其实就是在把当前子集的最右边的1的右边全部变为1,自己变为00,然后进行与运算把新增的1中不该出现的抹去,最后只剩下了原集合中存在的1了。

for(int S0 = S; S0; S0 = (S0-1)&S) //SO为S的子集
#include<cstdio>
#include<algorithm>
using namespace std;

const int maxn = 16;
int n, P[maxn], cover[1<<maxn], f[1<<maxn];
int main() {
  int kase = 0;
  while(scanf("%d", &n) == 1 && n) {
    for(int i = 0; i < n; i++) {
      int m, x;
      scanf("%d", &m);
      P[i] = 1<<i;
      while(m--) { 
		  scanf("%d", &x); 
		  P[i] |= (1<<x); 
	  }
    }
    for(int S = 0; S < (1<<n); S++) {
      cover[S] = 0;
      for(int i = 0; i < n; i++)
        if(S & (1<<i)) cover[S] |= P[i];
    }
    f[0] = 0;
    int ALL = (1<<n) - 1;
    for(int S = 1; S < (1<<n); S++) {
      f[S] = 0;
      for(int S0 = S; S0; S0 = (S0-1)&S)
        if(cover[S0] == ALL) f[S] = max(f[S], f[S^S0]+1);
    }
    printf("Case %d: %d\n", ++kase, f[ALL]);
  }
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42835910/article/details/90082562