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