来自《算法竞赛入门经典训练指南》
1.题目原文
假如你是一个黑客,侵入了n台计算机(编号为0,1,2,……n-1)的网络。一共有n种服务,每台计算机都运行着这种服务。对于每台计算机,你都可以选择一项服务,终止这台计算机和所有与它相邻的计算机的该项服务(如果一些服务已经停止,会一直停止下去)。目标是让尽量多的服务完全瘫痪(没有任何计算机运行该项服务).
2.解题思路
数学模型是:把n个集合P[1],P[2],……P[n]分成尽量多组,使得每组中所有集合的并集等于全集。对于本题,集合P[i]就是计算机i及其相邻的计算机的集合,每组对应题目中的一项服务。
集合的并集,用按位或;集合的差集,用按位异或。
显然用二进制表示集合。下面用状态压缩dp来解决这道题。
用cover[S]表示若干P[i]的集合S中所有P[i]的并集。
用f(S)表示子集S最多可以分多少组,则f(S)=max(f(S-S0)}+1.其中S0是S的子集,且cover[S0]等于全集。
这题有一个重要的技巧,枚举S的所有子集S0。
如何分析算法的时间复杂度?它等于全集{1,2,3,……,n}的所有子集的个数,显然就是sum{C(n,k)*2^k}=3^n。
3.AC代码
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <queue>
#include <string>
#include <set>
#include <vector>
#include<cmath>
#include<bitset>
#include<sstream>
#include<stack>
using namespace std;
#define INF 0x7fffffff
typedef long long ll;
const int maxn=(1<<16)+10;
//用cover[S]表示若干P[i]的集合S中所有P[i]的并集。
//用f(S)表示子集S最多可以分多少组
int P[maxn],cover[maxn],f[maxn];
int n,m;
int main()
{
int kase=0;
while(scanf("%d",&n)!=EOF&&n){
for(int i=0;i<n;i++){
scanf("%d",&m);
P[i]=1<<i;
while(m--){
int x;
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;
//枚举集合S的所有子集
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;
}