UVa 11825 Hackers’ Crackdown
题目大意:
侵入n台计算机,每台计算机有m个相邻计算机.对于一台计算机,可以选择终止其一种服务,相邻计算机也会受到影响停止该种服务,问最多能有多少种服务完全瘫痪(也就是说要让所有计算机均停止该种服务).
题目分析:
一台计算机只能终止一种服务,若用P[i]表示i号计算机的相邻计算机集合(包括i号计算机本身),那么实质上问题就是在每一组的P的并集为全集的前提下,将如何分组,使得组数尽可能多.
那么若用S表示P集合的取法,ALL表示全集,S0表示为S子集,其补集为S^S0.
则有,若S0=ALL,那么S0就可以作为一种分组.
那么S的分组种数=S0的分组种数+S^S0的分组种数.
设f[S]表示S集合最多能分成多少组,则可以表示为f[S]=1+f[S^S0].
所以转移方程f[S]=max{1+f[S^S0]|S=ALL}
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=16;
int P[maxn],cover[1<<maxn],f[1<<maxn],n;
//P[i]表示第i号计算机的相邻集合
//S表示P的集合,那么cover[S]表示S包含的计算机集合,f[S]表示S集合下的种类数
int main()
{
int kase=0;
while(scanf("%d",&n)==1&&n) {
for(int m,i=0;i<n;i++) {//预处理每台计算机的集合
scanf("%d",&m);
P[i]=1<<i;
for(int t,j=0;j<m;j++) scanf("%d",&t),P[i]|=(1<<t);
}
int ALL=(1<<n)-1;
for(int S=0;S<=ALL;S++) {//预处理S包含的计算机集合
cover[S]=0;
for(int i=0;i<n;i++) if(S&(1<<i)) cover[S]|=P[i];
}
for(int S=0;S<=ALL;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);//集合S由子集S0和S0的补集S^S0并而来
//若S0是全集ALL单独能构成一种合法方案加上其补集能构成的方案数,即f[S^S0]+1
}
printf("Case %d: %d\n",++kase,f[ALL]);
}
return 0;
}