图片加载可能有点慢,请跳过题面先看题解,谢谢
这道题目把模型简化后其实很简单,简化后的模型为:
有许多个小集合 \(P0,P2...Pn-1\) (即每个节点和与它相邻节点的并为 \(P\) ),现在要求出一种分组方式,使得每一组的元素的并为全集并且组数最多
$
$
我们注意到,\(n\) 很小,完全可以将 \(P\) 压成一个二进制数,那么这就是一个比较简单的子集 \(dp\) 了。
设 \(vis[i]\) 为,在 \(i\) 状态下的覆盖情况(\(i\) 是一个二进制数,表示 \(P\) 的选取状态);
设 \(f[i]\) 为, 在 \(i\) 状态下能破坏的最大服务数(\(i\) 同上),则答案为 \(f[(1<<n)-1]\);
$
$
那么转移就很好写了:$f[i]=max(f[i],f[i $ ^ j]+1)\(,其中,\)\(j\) 为 \(i\) 的子集且 \(vis[j]=(1<<n)-1\)
另附枚举 \(i\) 的子集 \(j\) 的方式:for(j=i;j;j=(j-1)&i)
//made by Hero_of_Someone
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define il inline
#define RG register
using namespace std;
il int gi(){ RG int x=0,q=1; RG char ch=getchar(); while( ( ch<'0' || ch>'9' ) && ch!='-' ) ch=getchar();
if( ch=='-' ) q=-1,ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; }
int t,n,p[54],vis[1<<20],f[1<<20];
il void init(){
for(int i=0;i<n;i++){
p[i]=1<<i; RG int m=gi();
for(int j=1;j<=m;j++){
RG int x=gi(); p[i]|=1<<x;
}
}
}
il void work(){
int ans=0,Max=(1<<n)-1;
for(int i=0;i<=Max;i++){ vis[i]=0;
for(int j=0;j<n;j++)
if(i&(1<<j)) vis[i]|=p[j];
}
f[0]=0;
for(int i=1;i<=Max;i++){ f[i]=0;
for(int j=i;j;j=(j-1)&i)
if(vis[j]==Max) f[i]=max(f[i],f[i xor j]+1);
}
printf("Case %d: %d\n",++t,f[Max]);
}
int main(){ while(scanf("%d",&n)&&n){ init(); work(); } return 0; }