3170. 【GDOI2013模拟4】挑选玩具

63 篇文章 0 订阅
8 篇文章 0 订阅

Description

ABC找到N个箱子,箱子里装着一些玩具,一共有M种玩具,编号从1到M,同一种玩具可能出现在多个箱子里。

ABC决定从中选择一些箱子,把这些箱子中的玩具聚集到一起,必须保证每种玩具至少出现一次。

问ABC一共有多少种选择方案。

Input

第一行输入两个整数N和M(1<=N<=1,000,000,1<=M<=20)

接下来N行,每行首先输入Ki(0<=ki<=M),接下来输入Ki个1到M之间互不相同的数,表示玩具的编号。

Output

输出一个整数表示选择方案 mod 1 000 000 007的结果。

Sample Input

输入1:

3 3

3 1 2 3

3 1 2 3

3 1 2 3



输入2:

3 3

1 1

1 2

1 3



输入3:

4 5

2 2 3

2 1 2

4 1 2 3 5

4 1 2 4 5

 

Sample Output

输出1:

7



输出2:

1



输出3:

6

Data Constraint

50%的数据满足 N<=100,M<=10

70%的数据满足 N<=1000,000,M<=15

100%的数据满足 N<=1000,000,M<=20

Solution

容斥原理。

答案=总方案数-非法方案

非法方案若何求?

我们发现若有一种情况其中有空的位置那么它一定是不可以的,所以对于一些箱子中若有一种或多种玩具都没有,那么这些箱子的所有组合方式都是非法的。

设f[ i ]表示状态为 i 时有多少个箱子中对应的位置没有这些种类的玩具,那么我们就可以算出这f[ i ]个箱子的组合都是非法的。

但是会有重复,当你的状态中只有一种玩具没有,你都将他们减去后,那些状态中有两种玩具没有的状态相当于多减了一次,所以在加回状态中有两种玩具没有的,以此类推。

但是重点是f[ i ]不好求,我们可以反过来想,我们存下读入的状态,然后在任意为0的位上加1,求出所有的方案数后,f[ i ]就相当于是状态 i 取反了,这样有什么好处呢?方便理解吧(当然你也可以减1),因为在我们0的位上加1的过程中就相当于状态数加了一个2的幂,比如0可以更新1,2,3,4...而7可以更新15,31....我们发现可以让一个数先在一个位置上加1,然后再在更新后的数上加2的幂,就类似一个宽搜的过程,其实我们又发现这个过程又很像分治,分治时,我们向两边递归,然后用左半边的来更新相对应的右半边的就能达到这种效果了。比如1,0 1 | 2 3 它可以更新3,然后 0 1 2 3 | 4 5 6 7,1又可以更新5,再用3更新7,这就相当于1更新7,以此类推,就能在O(2^m log 2^m)的时间复杂度内完成了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1000010
#define M 1000000007
#define ll long long
using namespace std;
int n,m,k,x,a[N];
ll ans,f[1048586];
ll ksm(ll k){
	if(k<2) return k+1;
	ll s=ksm(k>>1);s=s*s%M;
	if(k%2==1) return (s<<1)%M;
	return s;
}
void dg(int l,int r){
	int mid=(l+r)>>1;
	if(l==r) return;
	dg(l,mid);dg(mid+1,r);
	for(int i=0;i<=mid-l;i++){
		f[mid+i+1]+=f[l+i];
	}
}
void dfs(int x,int s,int t){
	if(x>m){
		if(!s) return;
		if(t%2==1) ans=(ans-(ksm(f[s^((1<<m)-1)])-1)+M)%M;
		else ans=(ans+(ksm(f[s^((1<<m)-1)])-1)+M)%M;
		return;
	}
	dfs(x+1,s,t);
	dfs(x+1,s+(1<<(x-1)),t+1);
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++){
		scanf("%d",&k);
		int s=0;
		for(int i=1;i<=k;i++){
			scanf("%d",&x);
			s+=1<<(x-1);
		}
		f[s]++;
	}
	dg(0,(1<<m)-1);
	ans=(ksm(n)-1+M)%M;
	dfs(1,0,0);
	printf("%lld\n",ans);
	return 0;
}


作者:zsjzliziyang 
QQ:1634151125 
转载及修改请注明 
本文地址:https://blog.csdn.nehttps://blog.csdn.net/zsjzliziyang/article/details/89525345

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值