集合计数(容斥原理)
题面
一个有N个元素的集合有2^N个不同子集(包含空集),现在要在这2^N个集合中取出若干集合(至少一个),使得它们的交集的元素个数为K,求取法的方案数,答案模1000000007。(是质数喔~)
思路
很容易想到先固定k个元素做交集,剩下n-k进行挑选,这个集合的所有子集2n-k个表示包含这k个元素的集合有这些个。我们再从这些集合中选若干个取交集,删去空集,方案有2^(2^(n-k))-1。
不难发现肯定是加多了的,于是按以上方法减k+1,结果又多减了,再加回来k+2……所以,这不就是容斥嘛
容斥原理
单步容斥
有的时候合法的方案数不好求,我们可以反向操作一波,用方案全集减掉不合法的方案数,就是答案
给定n和m,求Σ(1<=i<=n)Σ(1<=j<=m)(GCD(i,j)*2-1) n,m<=10W
令f[x]表示gcd(i,j)=x的数对(i,j)的数量,这个不是很好求
令g[x]表示x是gcd(i,j)的约数的数对(i,j)的数量,那么g[x]=(n/x)*(m/x)
考虑,如果x是gcd(i,j)的约数,但gcd(i,j)却不等于x,那么gcd(i,j)可能等于什么? 2x,3x,4x,... m 所以有f[x]=g[x]-f[2x]-f[3x]-f[4x]...在1~n的范围内枚举一个数再枚举他的倍数是O(nlogn)的
多步容斥
有时候剪掉不合法方案的时候会顺便减掉一些合法方案;将这些合法方案加回去,又会加回去一些不合法方案;反反复复,就是多步容斥。
比如说我们最熟悉的这个式子
有兴趣见百度证明
容斥更多是一种思想,而并非算法,但也是有一些套路的
话说回来
我们给出式子 ans+=(-1)^(i-k)*C(n,i)*(2^(2^(n-i))-1)*C(i,k);
各项的意义基本已在上面说过,最后的疑惑是C(i,k)。我们多加了几个k+1呢?我们挑选的这i个里,其实每个k都有多加,也就是C(i,k)——此项的系数,不太好懂,感性理解一下,找找规律。
另外,2^(2^(n-i))中,2^(n-i)为幂数,可不能直接模。这时候就用到费马小定理。让2^(n-i)%(mod-1)
幂数取模费小证明
xy%mod=x^(y%(mod-1))*x^(y-y%(mod-1))%mod=x^(y%(mod-1))*x^(y/(mod-1)*(mod-1))%mod
=x^(y%(mod-1))*(x^(mod-1))^(y/(mod-1))%mod;
由费马小定理得x^(mod-1)%mod=1,所以(x^(mod-1))^(y/(mod-1))%mod=1
所以xy%mod=x^(y%(mod-1))%mod;
证毕
代码
#include<bits/stdc++.h> using namespace std; #define ll long long const int MAX=1000010; const ll mod=1000000007; ll f[MAX],inv[MAX],n,k,ans; ll power(ll a,ll k,ll pr){ ll m=1; while(k){ if(k&1) m=m*a%pr; a=a*a%pr;k>>=1; }return m;//函数一定要有返回值 } ll C(int n,int m){ if(n<m) return 0; return f[n]*inv[m]%mod*inv[n-m]%mod; } int main(){ scanf("%d%d",&n,&k);f[0]=1; for(int i=1;i<=n;++i) f[i]=f[i-1]*i%mod; inv[n]=power(f[n],mod-2,mod); for(int i=n-1;i>=0;--i) inv[i]=inv[i+1]*(i+1)%mod; int p=1; for(int i=k;i<=n;++i){ ll fd=power(2,power(2,n-i,mod-1),mod)-1; ans=(ans+(mod+1ll*p*C(i,k)%mod*fd%mod*C(n,i)%mod)%mod)%mod; p=-p;//式子的符号很多,看清不要打成别的 }cout<<ans; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)