集合计数

题目描述

一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数喔~)

输入格式

一行两个整数N,K

输出格式

一行为答案。

样例

样例输入

3 2

样例输出

6

数据范围与提示

样例说明

假设原集合为{A,B,C}

则满足条件的方案为:{AB,ABC},{AC,ABC},{BC,ABC},{AB},{AC},{BC}

数据说明

对于100%的数据,1≤N≤1000000;0≤K≤N;

题解

看到这个题我们很自然的想到答案是

$\binom{n}{k}*f(n-k)$

其中f(i)表示i个元素的2i个集合中,选出任意多集合使交集为空的方案数,但是一个集合都不选是不合法的

一个暴力算法

显然f(0)=1

设g(i,j)表示从i个元素的集合中,选出任意多集合使交集为k个的方案数

$g(i,j)=\binom{i}{j}*f(i-j)$

对于i>1    $f(i)=2^{2^i}-1-\sum\limits_{j=1}^{i}g(i,j)$

注意不能一个集合都不选,但可以选择集合中没有任何元素的集合来组成一个对集合的集合,这涉及到-1的位置

复杂度O(n2)  期望得分70

正解 容斥原理

$f(n)=\sum\limits_{i=0}^{n}*(-1)^i*\binom{n}{i}*(2^{2^{n-i}}-1)$

集合A B C表示交集中含有 a,b,c的集合取法

C(n,i)表示从n个形如A B C的集合中取出i个,算出有多少种取法

这i个集合的交集则表示同时含有这i个元素

后一项则表示其他集合任意选取,但不能一个都不选的方案数

偶加奇减,则得到全集减去这几个集合的并集,得到f(i)

 #include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int mod=1e9+;
int n,k;
ll js[],jsinv[];
ll qpow(ll base,int y,int mo)
{
ll ans=;
while(y)
{
if(y&) ans=ans*base%mo;
base=base*base%mo;
y>>=;
}
return ans;
}
void init()
{
js[]=;
for(int i=;i<=n;i++) js[i]=js[i-]*i%mod;
jsinv[n]=qpow(js[n],mod-,mod);
for(int i=n-;i>=;i--) jsinv[i]=jsinv[i+]*(i+)%mod;
}
inline ll C(int n,int m)
{
return js[n]*jsinv[m]%mod*jsinv[n-m]%mod;
}
inline ll ask(int m)
{
ll ans=;
for(int i=,u=;i<=m;i++,u=-u)
ans=(ans+u*C(m,i)*(qpow(,qpow(,m-i,mod-),mod)-)%mod)%mod;
return ans;
}
int main()
{
scanf("%d%d",&n,&k);
init();
printf("%lld\n",(ask(n-k)*C(n,k)%mod+mod)%mod);
return ;
}

另一种等价的方法

$ans=\binom{n}{k}*\sum\limits_{i=k}^{n}(-1)^{i-k}*\binom{n-k}{i-k}*(2^{2^{n-i}}-1)$

这种方法可以理解为固定一种组合,从其他集合中选取几个进行容斥

也能算出答案

 #include<iostream>
#include<cstdio>
#define ll long long
using namespace std;
const int mod=1e9+;
int n,k;
ll js[],jsinv[];
ll qpow(ll base,int y,int mo)
{
ll ans=;
while(y)
{
if(y&) ans=ans*base%mo;
base=base*base%mo;
y>>=;
}
return ans;
}
void init()
{
js[]=;
for(int i=;i<=n;i++) js[i]=js[i-]*i%mod;
jsinv[n]=qpow(js[n],mod-,mod);
for(int i=n-;i>=;i--) jsinv[i]=jsinv[i+]*(i+)%mod;
}
inline ll C(int n,int m)
{
return js[n]*jsinv[m]%mod*jsinv[n-m]%mod;
}
int main()
{
scanf("%d%d",&n,&k);
init();
ll ans=;
for(int i=k,u=;i<=n;i++,u=-u)
ans=(ans+u*C(n-k,i-k)%mod*(qpow(,qpow(,n-i,mod-),mod)-)%mod)%mod;
printf("%lld\n",(ans*C(n,k)%mod+mod)%mod);
return ;
}

最新文章

  1. php调用COM组件
  2. jdbc java数据库连接 6)类路径读取——JdbcUtil的配置文件
  3. fatal error C1061: 编译器限制 : 块嵌套太深
  4. 关于oracle的准备
  5. DBHelper
  6. jQuery Pagination Plugin ajax分页控件
  7. c++,public/protected/private权限修饰符
  8. qsort函数辅助函数compare函数的编写
  9. jQuery对象插件封装步骤
  10. word/pdf互转的链接
  11. 排序算法C++实现
  12. [剑指Offer]29-顺时针打印矩阵-Java
  13. 如何方便的在windows测试python程序
  14. 温故知新 —— Floyd算法
  15. js限制图片大小、点击放大图片、点击在新开页面显示
  16. SpringMVC一例 是否需要重定向
  17. JDK中ConcurrentHashMap效率测试
  18. Android Studio打开项目提示找不到sdk路径的问题。
  19. Thunder团队第七周 - Scrum会议6
  20. libtorch 哪些函数比较常用?

热门文章

  1. 信安周报-第04周:系统函数与UDF
  2. Kafka学习笔记2--Kafka的服务端配置
  3. Linux 笔记 - 第二十四章 配置 Tomcat
  4. AspNet Core结合Quartz使用定时任务且通过注入缓存或者配置参数
  5. Form之action提交不刷新不跳转
  6. rabbitMQ基础应用
  7. golang-Json编码解码
  8. zsh禁用自动更新
  9. php7.3升级后CI框架session失效session不能读取的问题
  10. android 第三方开源库 学习汇总之Butter Knife