TOJ4505: KOSARE
TOJ4505: KOSARE 
Total Submit: 11 Accepted:3
Description
Mirko found N boxes with various forgotten toys at his attic. There are M different toys, numbered 1 through M, but each of those can appear multiple times across various boxes.
Mirko decided that he will choose some boxes in a way that there is at least one toy of each kind present, and throw the rest of the boxes away.
Determine the number of ways in which Mirko can do this.
Input
The first line of input contains two integers N and M (1 ≤ N ≤ 1 000 000, 1 ≤ M ≤ 20).
Each of the following N lines contains an integer Ki (0 ≤ Ki ≤ M) followed by Ki distinct integers from interval [1, M], representing the toys in that box.
Output
The first and only line of output should contain the requested number of ways modulo 1 000 000 007.
Sample Input
3 3
3 1 2 3
3 1 2 3
3 1 2 3
Sample Output
7
Source
你有n个盒子,然后有m种物品。每个盒子各有K个物品,你有多少种方式拿盒子,把东西都拿完
那天我的思路就是去统计这些盒子的状态然后去容斥,显然是爆炸的
这个要用到那个集合的那个知识,n个元素的他的子集有2^n个,也就是幂集,在这个基础上枚举子集容斥就可以省掉很多步骤
来看一下这个状态压缩的道理吧
之前做过求两个数字有相同数位的对数,那个把每个数直接变为2^digit,找到每一位的hash值,然后把每个数都减1,这样1对应1,2对应2,20对应2^19,这些数加起来是2^20-1,所以数组开到1<<20即可
每个盒子所对应的hash值知道了,我要和另一个盒子的东西看他们一样么,可以这样想,和他肯定不同的会是谁呢,就是从L开始到枚举的那个中间值(L+R)>>1,mi+i-L肯定是和i是相关的,要进行加法计算,但是我有些子集是重复的,需要枚举重新减去
#include<stdio.h>
typedef __int64 ll;
const ll MD=1e9+;
ll a[];
ll po(int n)
{
ll ans=,b=;
while(n)
{
if(n&)ans=ans*b%MD;
b=b*b%MD;
n>>=;
}
return ans;
}
void la(int L,int R)
{
if(L+==R)
{
a[L]=po(a[L]);
return;
}
int mi=(L+R)>>;
for(int i=L; i<mi; i++)
a[mi+i-L]+=a[i];
la(L,mi);
la(mi,R);
for(int i=L; i<mi; i++)
{
a[mi+i-L]-=a[i];
if(a[mi+i-L]<)a[mi+i-L]+=MD;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=,k; i<n; i++)
{
scanf("%d",&k);
int f=;
for(int j=,x; j<k; j++)
{
scanf("%d",&x);
f|=(<<(--x));
}
a[f]++;
}
la(,<<m);
printf("%I64d",a[(<<m)-]);
}
这个怎么省去加这个状态呢,就是提前进行一次二进制枚举
#include<stdio.h>
typedef __int64 ll;
const ll MD=1e9+;
ll a[];
ll po(int n)
{
ll ans=,b=;
while(n)
{
if(n&)ans=ans*b%MD;
b=b*b%MD;
n>>=;
}
return ans;
}
void la(int L,int R)
{
if(L+==R)
{
a[L]=po(a[L]);
return;
}
int mi=(L+R)>>;
la(L,mi);
la(mi,R);
for(int i=L; i<mi; i++)
{
a[mi+i-L]-=a[i];
if(a[mi+i-L]<)a[mi+i-L]+=MD;
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
int ff=<<m,f;
for(int i=,k; i<n; i++)
{
scanf("%d",&k);
f=;
for(int j=,x; j<k; j++)
{
scanf("%d",&x);
f|=(<<(--x));
}
a[f]++;
}
for(int i=; i<m; i++)
for(int s=; s<ff; s++)
if(s&(<<i))
a[s]+=a[s^(<<i)];
la(,ff);
printf("%I64d",a[ff-]);
}
直接位运算去统计,这个算法速度最快
#include<stdio.h>
const int N=<<,MD=1e9+;
int cnt[N],B[N],n,m,ans[N],f;
int main()
{
scanf("%d%d",&n,&m);
f=<<m;
B[]=;
for(int i=; i<=n; i++)
B[i]=(B[i-]<<)%MD;
for(int i=,t,x,s; i<n; i++)
{
scanf("%d",&t);
s=;
while(t--)
scanf("%d",&x),s|=<<x-;
cnt[s]++;
}
for(int i=; i<m; i++)
for(int s=; s<f; s++)
if(s&<<i)
cnt[s]+=cnt[s^<<i];
for(int s=; s<f; s++)
ans[s]=B[cnt[s]];
for(int i=; i<m; i++)
for(int s=; s<f; s++)
if(s&<<i)
ans[s]=(ans[s]-ans[s^<<i]+MD)%MD;
printf("%d",ans[f-]);
return ;
}
TOJ4505: KOSARE的更多相关文章
随机推荐
- cpp 计算程序运行时间的两种方法
1. #include <time.h> time_t begin_t = clock(); // to do time_t finish_t = clock(); cout<< ...
- 读写属性/if判断那点事/数组
读写属性属性操作注意事项 js中不允许出现"-".所以font-size变成fontSize/margin-top变成marginTop. Op.style.with=" ...
- Linux:linux下建ftp用户,并限制用户访问路径
安装:ftp安装部分,操作步骤如下: 可以使用yum命令直接安装ftp # yum install vsftpd ftp服务的开启与关闭命令: 开启:# service vsftpd start 关闭 ...
- Windows 7, Visual Studio 2015下编译Webkit
因工作需要,需要编译Windows版本的Webkit,中间走了不少弯路,都记录下来,供大家参考!也随时欢迎大家讨论(QQ群:345802342) 整个编译工作参考的是官方文档:https://webk ...
- 如何在Sierra运行 Specials K 的patch
https://github.com/ApolloZhu/CORE-Keygen-and-Special-K-for-Sierra-Utility/blob/master/Special%20K%20 ...
- 使用Java connector消费ABAP系统的函数
Java Connector(JCO)环境的搭建:Step by step to download and configure JCO in your laptop 我的ABAP系统有个函数名叫ZDI ...
- Windows 漏洞利用开发
第一阶段:简单栈溢出 分析栈溢出原理 寻找溢出点,了解pattern_create和pattern_offset计算溢出点的原理 寻找JMP ESP跳板,分析利用JMP ESP跳板劫持程序流的原理 编 ...
- (原)IPhone开发时把ToolBar中的元素居中的技巧
在IPhone应用开发时,经常用到ToolBar,其中的控件通常都是居左,想让它居中就有点困难. 这里介绍一种方法: 将Flexible Space Bar Button Item从库中拖到位于控件左 ...
- C#语言命名的9种规范
下面介绍C#语言命名的9种规范: a) 类 [规则1-1]使用Pascal规则命名类名,即首字母要大写. [规则1-2]使用能够反映类功能的名词或名词短语命名类. [规则1-3]不要使用“I”.“C” ...
- c++作业:使用函数调用的方法,实现求两个整数中大的数的程序。
#include <iostream> using namespace std; int main(){ //从键盘接收两个整数,保存在变量num1和num2中 cout<<& ...