黑客的攻擊(Hacker's Crackdown, UVa 11825)狀壓dp枚舉子集

題目鏈接
假設你是一個黑客, 侵入了一個有着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;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章