Source
https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2925
Solution
首先要对题目做一个等价转换,题目等价于将集合
{{p1},{p2}...{pn}}
{
{
p
1
}
,
{
p
2
}
.
.
.
{
p
n
}
}
划分成若干个子集,使尽量多的子集中集合的并集都等于全集
U
U
,表示
n
n
个节点的并集,集合是每个点和它相邻节点构成的集合。
这是一道状压
DP
D
P
,用
f[i]
f
[
i
]
表示状态为
i
i
时的最大获利,其中状态的每个二进制位记录的是这个集合选或没选,
状态转移方程为
f[S]=max{f[S−S0]+1,S0是S的子集且S0包含了全部
f
[
S
]
=
m
a
x
{
f
[
S
−
S
0
]
+
1
,
S
0
是
S
的
子
集
且
S
0
包
含
了
全
部
n
个节点}
个
节
点
}
这个方程的理解就是,对于集合
S
S
,我枚举它的子集,看看这个子集是不是能够完全包含个节点,如果可以,就更新答案。
对于集合的
check
c
h
e
c
k
,看他是不是包含了全部
n
n
个点,可以提前与处理,这样复杂度就是枚举和子集的复杂度
S
S
一共有种,但是对于每种集合,
S0
S
0
的数量不同,
S0
S
0
的数量应该是
2|S|
2
|
S
|
用组合数算一下,
∑ni=1Cin2i=∑ni=02i1n−i−1
∑
i
=
1
n
C
n
i
2
i
=
∑
i
=
0
n
2
i
1
n
−
i
−
1
=
3n−1
3
n
−
1
所以时间复杂度是
Θ(3n)
Θ
(
3
n
)
316=43,046,721
3
16
=
43
,
046
,
721
数量级高达
107
10
7
,但是因为我们在做位运算,因此很快
取模如果这么大时间复杂度的话,肯定已经超时了
Code
#include <cstdio>
#include <algorithm>
#include <cstring>
#define clear(x) memset(x,0,sizeof(x))
#define maxn 20
using namespace std;
int n, p[maxn], f[1<<16], ok[1<<16];
bool check(int S)
{
int t, i;
for(i=t=0;i<n;i++)if(S&(1<<i))t|=p[i];
return t==(1<<n)-1;
}
bool init()
{
int i, m, x;
scanf("%d",&n);
if(n==0)return 0;
clear(p);
clear(ok);
for(i=0;i<n;i++)for(scanf("%d",&m),p[i]=1<<i;m--;)scanf("%d",&x),p[i]|=1<<x;
for(i=0;i<1<<n;i++)if(check(i))ok[i]=1;
return 1;
}
void dp()
{
int S, s;
clear(f);
for(S=0;S<1<<n;S++)for(s=S;s;s=(s-1)&S)if(ok[s])f[S]=max(f[S],f[S^s]+1);
}
int main()
{
int c;
for(c=1;init();c++)
{
printf("Case %d: ",c);
dp();
printf("%d\n",f[(1<<n)-1]);
}
return 0;
}