P1502【COCI 2011/2012 CONTEST #6-4】盒子与玩具
时间限制 : 10000 MS 空间限制 : 65536 KB
问题描述
Mirko找到了N个盒子,盒子里面放了一些玩具。一共有M种不同的玩具,每种玩具可能有多个。同一种玩具可以出现在不同的盒子里。现在Mirko想选择一些盒子,使得每种玩具都至少有被选择。问Mirko有多少种选择的方法。
输入格式
第一行有2个整数N,M(1<=N<=1000000,1<=M<=20)
接下来N行,每行有若干个数。第一个数为Ki(0<=ki<=M),表示该盒子中有Ki种玩具。接下来有Ki个不同的数,表示有哪些种类的玩具。
输出格式
只有一行,表示方案总数模1000000007的值。
样例输入
3 3
3 1 2 3
3 1 2 3
3 1 2 3
样例输出
7
提示
50%的数据,N<=100,M<=15
70%的数据,N<=1000000 ,M<=15
分析:
方法一:暴力深搜 ,20分走好。
方法二: DP
f[s] 表示选出盒子并集为S的方案数。
g[s]表示输入时统计的 集合为S的盒子的数量。
对g[s]进行枚举子集求和得到G[s].即G[s]= cigema{g[ T ]} (T是S的子集)
G[s]表示S及其子集代表的盒子数量。
对f[s]进行枚举子集求和得到F[s]. 即F[s]= cigema{f[ T ]} (T是S的子集)
F[s]表示选出盒子并集为 S或其子集 的方案数。
惊奇地发现F[s]= 2^G[s].
于是可以先求G[s],再求F[s],时间复杂度为O(3^m):70分走你。
方法三:
思路与方法二一样,方法二的时间花在了枚举子集上。
考虑这样的方法:
每一次讨论某一个元素,设S含有i号元素。
g[s]+=g[ s^(1<<i) ] ,这样原地求出了G[s].
求f[s]时,改成-=即可(想一想为什么)。
时间复杂度降到O(2^ m): 100分拿好。
代码如下:
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
#include<algorithm>
#define LL long long
#define CLEAR(xxx) memset(xxx,0,sizeof(xxx))
using namespace std;
const LL maxn=1000000+5,inf=1e9,mod=1000000007,size=23;
LL n,m,All,f[(1<<size)+5],g[(1<<size)+5];
LL pow_2[maxn];
inline void _read(LL &x){
char ch=getchar(); bool mark=false;
for(;!isdigit(ch);ch=getchar())if(ch=='-')mark=true;
for(x=0;isdigit(ch);ch=getchar())x=x*10+ch-'0';
if(mark)x=-x;
}
int main(){
//freopen("A.in","r",stdin);
//freopen("A.out","w",stdout);
LL s0,i,k,x,y;
_read(n) ;_read(m);
pow_2[0]=1;
for(i=1;i<=n;i++){
_read(k);
pow_2[i]=(pow_2[i-1]<<1)%mod;
y=0;
while(k--){
_read(x); x--;
y|=(1LL<<x);
}
g[y]++;
}
All=(1<<m)-1;
for(i=0;i<=m;i++)
for(s0=0;s0<=All;s0++)
if(s0&(1<<i))
(g[s0]+=g[s0^(1<<i)])%=mod;
for(s0=0;s0<=All;s0++)
f[s0]=pow_2[g[s0]];
for(i=0;i<=m;i++)
for(s0=0;s0<=All;s0++)
if(s0&(1<<i))
f[s0]=(f[s0]-f[s0^(1<<i)]+mod)%mod;
cout<<f[All]%mod<<endl;
return 0;
}