正题
题目链接:https://www.luogu.com.cn/problem/AT4119
题目大意
一个集合\(S=\{k\in[1,n]\cup N\}\),它的所有子集作为元素组成的集合中要求满足每一个数字的出现之和不小于\(2\),求方案数对\(P\)取模。
\(1\leq n\leq 3000,P\in[10^8,10^{9}+9]\cup Pri\)
解题思路
考虑至少\(i\)个数选择次数不超过\(1\),那么这个方案的容斥系数就是\((-1)^i\)。
考虑怎么求这个方案,我们可以先不要被限制了的数,然后再将这些被限制了的数丢进被选出了的集合中。设有\(j\)个集合包含被限制了的数,那么丢进这些集合的方案就是\(\begin{Bmatrix} i+1\\j+1 \end{Bmatrix}\)(一个数字可以选择不丢所以开一个新的集合表示这个集合内的数不使用),然后剩下的数随意的选入这些集合中就是\((2^{n-i})^j\)。
那么答案出来了
\[\sum_{i=0}^n(-1)^i2^{2^{n-i}}\binom{n}{i}\sum_{j=0}^i\begin{Bmatrix}i+1\\ j+1\end{Bmatrix}(2^{n-i})^j
\]
直接预处理斯特林数计算就好了,时间复杂度\(O(n^2)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=3100;
ll n,P,s[N][N],fac[N],ans;
ll power(ll x,ll b,ll p=P){
ll ans=1;
while(b){
if(b&1)ans=ans*x%p;
x=x*x%p;b>>=1;
}
return ans;
}
ll C(ll n,ll m)
{return fac[n]*power(fac[m],P-2)%P*power(fac[n-m],P-2)%P;}
signed main()
{
scanf("%lld%lld",&n,&P);s[0][0]=fac[0]=1;
for(ll i=1;i<=n;i++)fac[i]=fac[i-1]*i%P;
for(ll i=1;i<=n+1;i++)
for(ll j=1;j<=i;j++)
s[i][j]=(s[i-1][j-1]+j*s[i-1][j]%P)%P;
for(ll i=0;i<=n;i++){
ll sum=0,tmp=power(2,power(2,n-i,P-1));
if(i&1)tmp=P-tmp;tmp=tmp*C(n,i)%P;
for(ll j=0,z=1,p=power(2,n-i);j<=i;j++,z=z*p%P)
(sum+=s[i+1][j+1]*z%P)%=P;
(ans+=sum*tmp)%=P;
}
printf("%lld\n",ans);
return 0;
}